Verwendung eines Objekts ohne Aufruf der Destruktor-Methode

Beschreibung

Verwendung eines Objekts ohne Aufruf der Destruktor-Methode tritt auf, wenn Code ein Objekt verwendet, aber danach nicht seine zugehörige Finalizer- oder Destruktor-Methode aufruft. Dieses Ressourcenverwaltungsproblem bedeutet, dass Bereinigungsoperationen vernachlässigt werden, was dazu führt, dass Ressourcen länger als nötig gehalten werden. In der objektorientierten Programmierung sind Destruktoren entscheidend für die Freigabe von Ressourcen wie Speicher, Datei-Handles, Netzwerkverbindungen und Datenbankverbindungen. Wenn sie nicht ordnungsgemäß aufgerufen werden, kann dies zu Ressourcenerschöpfung führen.

Risiko

Das Versäumnis, Destruktor-Methoden aufzurufen, hat Sicherheitsimplikationen. Speicher und andere Ressourcen werden länger als nötig gehalten, was zu Leistungsverschlechterung führt. Über die Zeit kann dies Ressourcenerschöpfung verursachen, die zu Denial of Service führt. Datei-Handles, Datenbankverbindungen und andere endliche Ressourcen können erschöpft werden. Sicherheitskritische Bereinigung wie das Löschen von Anmeldedaten aus dem Speicher erfolgt möglicherweise nicht. Sperrdateien werden möglicherweise nicht freigegeben, was andere Prozesse blockiert. Angreifer können vorhersehbare Ressourcenlecks ausnutzen, um Systemressourcen zu erschöpfen. Langlebige Anwendungen sind besonders anfällig, da sich Lecks ansammeln.

Lösung

Stellen Sie immer sicher, dass Destruktor- oder Finalizer-Methoden aufgerufen werden, wenn Objekte nicht mehr benötigt werden. Verwenden Sie Sprachkonstrukte, die Bereinigung garantieren, wie try-with-resources in Java, using-Anweisungen in C#, Kontextmanager in Python oder RAII in C++. Implementieren Sie ordnungsgemäße Ressourcenverwaltungsmuster. Verwenden Sie Smart Pointer in C++, um die Zerstörung zu automatisieren. Verlassen Sie sich auf automatische Garbage Collection wo verfügbar, aber hängen Sie nicht davon für kritische Bereinigung ab. Implementieren und testen Sie Bereinigungscodepfade. Verwenden Sie statische Analysetools, um fehlende Bereinigungsaufrufe zu erkennen. Dokumentieren Sie Ressourcenlebenszyklus-Anforderungen für benutzerdefinierte Klassen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Länger als nötig gehaltene Ressourcen können zur Erschöpfung führen.
AndereBereich: Ändere

Reduzierte Leistung - Speicher- und Ressourcenlecks verschlechtern die Leistung über die Zeit.
VertraulichkeitBereich: Vertraulichkeit

Informationspreisgabe - Sensible Daten können im Speicher verbleiben, wenn Bereinigung nicht durchgeführt wird.

Beispielcode

Anfälliger Code

// Anfällig: Objekt verwendet ohne Destruktoraufruf
class VulnerableResourceHolder {
private:
    FILE* file;
    char* buffer;
    DatabaseConnection* dbConn;

public:
    VulnerableResourceHolder(const char* filename, size_t bufSize) {
        file = fopen(filename, "r");
        buffer = new char[bufSize];
        dbConn = new DatabaseConnection("localhost:5432");
    }

    ~VulnerableResourceHolder() {
        if (file) fclose(file);
        delete[] buffer;
        delete dbConn;
    }

    void processData() {
        // Daten mit Ressourcen verarbeiten
    }
};

void vulnerableUsage() {
    // Anfällig: Mit new allokiert aber nie gelöscht
    VulnerableResourceHolder* holder =
        new VulnerableResourceHolder("data.txt", 4096);

    holder->processData();

    // BUG: Vergessen holder zu löschen!
    // Destruktor wird nie aufgerufen
    // Ressourcen geleakt: Datei-Handle, Buffer, DB-Verbindung
}

void vulnerableArrayUsage() {
    // Anfällig: Array von Objekten
    VulnerableResourceHolder* holders =
        new VulnerableResourceHolder[10];

    // Objekte verwenden...

    // BUG: delete anstelle von delete[] verwendet
    delete holders;  // Nur erster Destruktor aufgerufen!
    // 9 Objekte haben geleakte Ressourcen
}
// Anfällig: Ressourcen in Java nicht ordnungsgemäß bereinigt
public class VulnerableResourceManager {

    private Connection dbConnection;
    private FileInputStream fileStream;
    private Socket socket;

    public VulnerableResourceManager(String dbUrl, String filePath, String host)
            throws Exception {
        dbConnection = DriverManager.getConnection(dbUrl);
        fileStream = new FileInputStream(filePath);
        socket = new Socket(host, 8080);
    }

    public void processData() throws Exception {
        // Mit Ressourcen verarbeiten
        Statement stmt = dbConnection.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM data");
        // ...
    }

    // Hat eine close-Methode aber...
    public void close() throws Exception {
        if (dbConnection != null) dbConnection.close();
        if (fileStream != null) fileStream.close();
        if (socket != null) socket.close();
    }
}

class VulnerableClient {

    public void processFile(String dbUrl, String filePath, String host) {
        try {
            // Anfällig: close() wird nie aufgerufen!
            VulnerableResourceManager manager =
                new VulnerableResourceManager(dbUrl, filePath, host);

            manager.processData();

            // Vergessen manager.close() aufzurufen!
            // DB-Verbindung, Datei und Socket alle geleakt

        } catch (Exception e) {
            // Noch schlimmer: Ausnahme bedeutet close() definitiv nicht aufgerufen
            e.printStackTrace();
        }
    }
}
# Anfällig: Python-Objekte ohne ordnungsgemäße Bereinigung
class VulnerableFileProcessor:
    def __init__(self, filename):
        self.file = open(filename, 'r')
        self.connection = create_database_connection()
        self.temp_files = []

    def __del__(self):
        # Destruktor existiert aber wird möglicherweise nie aufgerufen!
        self.file.close()
        self.connection.close()
        for temp in self.temp_files:
            os.remove(temp)

    def process(self):
        # Erstellt temp-Dateien während der Verarbeitung
        temp = tempfile.NamedTemporaryFile(delete=False)
        self.temp_files.append(temp.name)
        # Daten verarbeiten...


def vulnerable_usage():
    # Anfällig: Verlasst sich auf Garbage Collector für __del__-Aufruf
    processor = VulnerableFileProcessor("data.txt")
    processor.process()

    # Keine explizite Bereinigung!
    # __del__ wird möglicherweise nicht sofort aufgerufen
    # Oder wird gar nicht aufgerufen bei zirkularen Referenzen
    # Oder wenn der Interpreter abnormal beendet wird


def vulnerable_exception_handling():
    processor = VulnerableFileProcessor("data.txt")
    try:
        processor.process()
        raise ValueError("Etwas ist schiefgelaufen")
    except ValueError:
        # Anfällig: processor nicht bei Ausnahme bereinigt
        pass
    # Ressourcen geleakt!

Korrigierter Code

// Korrigiert: Ordnungsgemäßer Destruktoraufruf mit RAII
class FixedResourceHolder {
private:
    std::unique_ptr<FILE, decltype(&fclose)> file;
    std::unique_ptr<char[]> buffer;
    std::unique_ptr<DatabaseConnection> dbConn;

public:
    FixedResourceHolder(const char* filename, size_t bufSize)
        : file(fopen(filename, "r"), &fclose),
          buffer(std::make_unique<char[]>(bufSize)),
          dbConn(std::make_unique<DatabaseConnection>("localhost:5432")) {

        if (!file) {
            throw std::runtime_error("Konnte Datei nicht öffnen");
        }
    }

    // Destruktor automatisch aufgerufen, Ressourcen automatisch bereinigt
    ~FixedResourceHolder() = default;

    // Nicht kopierbar um Double-Free zu verhindern
    FixedResourceHolder(const FixedResourceHolder&) = delete;
    FixedResourceHolder& operator=(const FixedResourceHolder&) = delete;

    // Verschiebbar
    FixedResourceHolder(FixedResourceHolder&&) = default;
    FixedResourceHolder& operator=(FixedResourceHolder&&) = default;

    void processData() {
        // Daten mit Ressourcen verarbeiten
    }
};

void fixedUsage() {
    // Korrigiert: Stack-Allokation - Destruktor automatisch aufgerufen
    FixedResourceHolder holder("data.txt", 4096);
    holder.processData();
    // Destruktor automatisch aufgerufen wenn Scope verlassen wird
}

void fixedHeapUsage() {
    // Korrigiert: Smart Pointer stellt Zerstörung sicher
    auto holder = std::make_unique<FixedResourceHolder>("data.txt", 4096);
    holder->processData();
    // Destruktor aufgerufen wenn unique_ptr out of scope geht
}

void fixedArrayUsage() {
    // Korrigiert: Vector für automatische Bereinigung verwenden
    std::vector<FixedResourceHolder> holders;
    holders.reserve(10);

    for (int i = 0; i < 10; i++) {
        holders.emplace_back("data.txt", 4096);
    }
    // Alle Destruktoren automatisch aufgerufen
}
// Korrigiert: Java mit try-with-resources
public class FixedResourceManager implements AutoCloseable {

    private final Connection dbConnection;
    private final FileInputStream fileStream;
    private final Socket socket;

    public FixedResourceManager(String dbUrl, String filePath, String host)
            throws Exception {
        // Ressourcen initialisieren - wenn welche fehlschlagen, bereits geöffnete schließen
        Connection tempDb = null;
        FileInputStream tempFile = null;

        try {
            tempDb = DriverManager.getConnection(dbUrl);
            tempFile = new FileInputStream(filePath);
            this.socket = new Socket(host, 8080);
            this.dbConnection = tempDb;
            this.fileStream = tempFile;
        } catch (Exception e) {
            // Partielle Initialisierung bereinigen
            if (tempDb != null) tempDb.close();
            if (tempFile != null) tempFile.close();
            throw e;
        }
    }

    public void processData() throws Exception {
        try (Statement stmt = dbConnection.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM data")) {
            // Ergebnisse verarbeiten
        }
    }

    @Override
    public void close() throws Exception {
        Exception firstException = null;

        // Alle Ressourcen schließen, Ausnahmen sammeln
        try {
            if (socket != null) socket.close();
        } catch (Exception e) {
            firstException = e;
        }

        try {
            if (fileStream != null) fileStream.close();
        } catch (Exception e) {
            if (firstException == null) firstException = e;
        }

        try {
            if (dbConnection != null) dbConnection.close();
        } catch (Exception e) {
            if (firstException == null) firstException = e;
        }

        if (firstException != null) throw firstException;
    }
}

class FixedClient {

    public void processFile(String dbUrl, String filePath, String host) {
        // Korrigiert: try-with-resources garantiert close()-Aufruf
        try (FixedResourceManager manager =
                new FixedResourceManager(dbUrl, filePath, host)) {

            manager.processData();

        } catch (Exception e) {
            // Auch wenn Ausnahme auftritt, wird close() aufgerufen
            logger.error("Verarbeitung fehlgeschlagen", e);
        }
        // Ressourcen immer bereinigt
    }
}
# Korrigiert: Python mit Kontextmanagern
class FixedFileProcessor:
    def __init__(self, filename):
        self._filename = filename
        self._file = None
        self._connection = None
        self._temp_files = []

    def __enter__(self):
        self._file = open(self._filename, 'r')
        self._connection = create_database_connection()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # Bereinigung garantiert auch wenn Ausnahme auftritt
        if self._file:
            self._file.close()
        if self._connection:
            self._connection.close()
        for temp in self._temp_files:
            try:
                os.remove(temp)
            except OSError:
                pass
        return False  # Ausnahmen nicht unterdrücken

    def process(self):
        temp = tempfile.NamedTemporaryFile(delete=False)
        self._temp_files.append(temp.name)
        # Daten verarbeiten...


def fixed_usage():
    # Korrigiert: Kontextmanager stellt Bereinigung sicher
    with FixedFileProcessor("data.txt") as processor:
        processor.process()
    # __exit__ automatisch aufgerufen, auch wenn Ausnahme auftritt


def fixed_exception_handling():
    try:
        with FixedFileProcessor("data.txt") as processor:
            processor.process()
            raise ValueError("Etwas ist schiefgelaufen")
    except ValueError:
        pass
    # Ressourcen ordnungsgemäß bereinigt trotz Ausnahme


# Alternative: contextlib für einfachere Fälle verwenden
from contextlib import contextmanager

@contextmanager
def managed_resource(filename):
    file = open(filename, 'r')
    conn = create_database_connection()
    try:
        yield file, conn
    finally:
        file.close()
        conn.close()


def using_contextmanager():
    with managed_resource("data.txt") as (file, conn):
        # Ressourcen verwenden
        pass
    # Bereinigung automatisch

CVE-Beispiele

Ressourcenleck-Schwachstellen durch fehlende Bereinigung haben zu Denial-of-Service-Bedingungen in vielen Anwendungen beigetragen, obwohl CVEs typischerweise die resultierende Auswirkung beschreiben anstatt diese spezifische Ursache.


Verwandte CWEs

  • CWE-772: Missing Release of Resource after Effective Lifetime (Eltern)
  • CWE-1076: Insufficient Adherence to Expected Conventions (Eltern)
  • CWE-401: Missing Release of Memory after Effective Lifetime (ähnlich)

Referenzen

  1. MITRE Corporation. "CWE-1091: Use of Object without Invoking Destructor Method." https://cwe.mitre.org/data/definitions/1091.html
  2. C++ Core Guidelines. R.11: Avoid calling new and delete explicitly.
  3. CISQ Quality Measures - Performance Efficiency.