Mehrfachvererbung von konkreten Klassen

Beschreibung

Mehrfachvererbung von konkreten Klassen tritt auf, wenn eine Klasse von mehr als einer konkreten (nicht-abstrakten) Klasse erbt. Während einige Sprachen wie C++ und Python Mehrfachvererbung unterstützen, erzeugt das Erben von mehreren konkreten Klassen Komplexität durch potenzielle Diamant-Vererbungsprobleme, Methodenauflösungsmehrdeutigkeiten und enge Kopplung an mehrere Implementierungen. Dieses Muster macht Code schwerer zu verstehen, zu testen und zu warten und kann zu unerwartetem Verhalten führen, wenn Elternklassen modifiziert werden.

Risiko

Obwohl primär ein Code-Qualitätsproblem, hat Mehrfachvererbung von konkreten Klassen indirekte Sicherheitsimplikationen. Die eingeführte Komplexität erschwert Sicherheitsaudits, da Prüfer mehrere Vererbungshierarchien verstehen müssen. Methodenauflösungsreihenfolge-Mehrdeutigkeiten können dazu führen, dass sicherheitskritische Methoden von unerwarteten Elternklassen aufgerufen werden. Änderungen an jeder Elternklasse können unerwartet das Sicherheitsverhalten beeinflussen. Das Testen wird komplexer, was möglicherweise Sicherheitsprobleme unentdeckt lässt. Die enge Kopplung an mehrere Implementierungen erschwert es, einzelne Komponenten zu isolieren und zu sichern.

Lösung

Bevorzugen Sie Komposition gegenüber Vererbung. Verwenden Sie Schnittstellen (oder rein abstrakte Klassen) zum Definieren von Verträgen und komponieren Sie konkrete Implementierungen. Wenn Mehrfachvererbung wirklich erforderlich ist, erben Sie von höchstens einer konkreten Klasse und verwenden Sie Schnittstellen/abstrakte Klassen für zusätzliche Fähigkeiten. Wenden Sie das Interface Segregation Principle an, um Schnittstellen fokussiert zu halten. Verwenden Sie Mixins vorsichtig und stellen Sie sicher, dass sie keinen veränderlichen Zustand tragen. Erwägen Sie die Verwendung von Traits oder Protocols, wo Sprachunterstützung existiert. Dokumentieren Sie die Vererbungshierarchie klar, wenn Mehrfachvererbung verwendet wird.

Häufige Auswirkungen

AuswirkungDetails
SonstigesBereich: Sonstiges

Reduzierte Wartbarkeit - Mehrfachvererbung von konkreten Klassen erzeugt Komplexität, die die Codebasis schwerer zu verstehen und sicher zu warten macht.
SonstigesBereich: Sonstiges

Qualitätsverschlechterung - Methodenauflösungsmehrdeutigkeiten und enge Kopplung können subtile Fehler einschließlich Sicherheitsschwachstellen einführen.

Beispielcode und Lösung

Verwundbarer Code

// Verwundbar: Mehrfachvererbung von konkreten Klassen in C++

// Konkrete Klasse 1
class DateiLogger {
protected:
    std::ofstream logDatei;
    std::string logPfad;

public:
    DateiLogger(const std::string& pfad) : logPfad(pfad) {
        logDatei.open(pfad, std::ios::app);
    }

    virtual void log(const std::string& nachricht) {
        logDatei << "[LOG] " << nachricht << std::endl;
    }

    virtual ~DateiLogger() {
        logDatei.close();
    }
};

// Konkrete Klasse 2
class NetzwerkClient {
protected:
    int socket_fd;
    std::string serverAdresse;

public:
    NetzwerkClient(const std::string& server) : serverAdresse(server) {
        connect();
    }

    virtual void senden(const std::string& daten) {
        // Daten über Netzwerk senden
    }

    virtual ~NetzwerkClient() {
        close(socket_fd);
    }
};

// Verwundbar: Erbt von zwei konkreten Klassen
class VerwundbarerRemoteLogger : public DateiLogger, public NetzwerkClient {
public:
    // Diamant-Problem-Potenzial, wenn beide Eltern eine gemeinsame Basis teilen
    // Mehrdeutigkeit: Welcher Eltern-Destruktor wird zuerst aufgerufen?
    // Komplexität: Zustand von zwei Eltern pflegen

    VerwundbarerRemoteLogger(const std::string& pfad, const std::string& server)
        : DateiLogger(pfad), NetzwerkClient(server) {}

    void log(const std::string& nachricht) override {
        // Welche Elternmethode hat Vorrang?
        // Was wenn beide widersprüchlichen Zustand haben?
        DateiLogger::log(nachricht);
        NetzwerkClient::senden(nachricht);
    }

    // Probleme:
    // 1. Enge Kopplung an zwei konkrete Implementierungen
    // 2. Beide Eltern verwalten Ressourcen (Dateien, Sockets)
    // 3. Destruktionsreihenfolge kann Probleme verursachen
    // 4. Änderungen an einem Elternteil können diese Klasse brechen
};
# Verwundbar: Mehrfachvererbung von konkreten Klassen in Python

class DatenbankVerbindung:
    """Konkrete Klasse mit Zustand und Implementierung"""

    def __init__(self, verbindungs_string):
        self.verbindungs_string = verbindungs_string
        self.verbindung = self._verbinden()
        self._transaktion_aktiv = False

    def _verbinden(self):
        # Datenbankverbindung herstellen
        return create_connection(self.verbindungs_string)

    def ausführen(self, abfrage):
        return self.verbindung.execute(abfrage)


class CacheManager:
    """Eine weitere konkrete Klasse mit Zustand"""

    def __init__(self, cache_große):
        self.cache = {}
        self.max_große = cache_große
        self._treffer = 0
        self._fehlschlaege = 0

    def holen(self, schlüssel):
        if schlüssel in self.cache:
            self._treffer += 1
            return self.cache[schlüssel]
        self._fehlschlaege += 1
        return None

    def setzen(self, schlüssel, wert):
        if len(self.cache) >= self.max_große:
            self._entfernen()
        self.cache[schlüssel] = wert


# Verwundbar: Erbt von zwei konkreten Klassen
class VerwundbareGecachteDatenbank(DatenbankVerbindung, CacheManager):
    """
    Probleme:
    1. Beide Eltern haben __init__ mit verschiedenen Parametern
    2. MRO-Komplexität - welches __init__ wird aufgerufen?
    3. Beide Eltern haben internen Zustand, der kollidieren kann
    4. Änderungen an einer Elternklasse betreffen diese Klasse
    """

    def __init__(self, verbindungs_string, cache_große):
        # Muss explizit beide Eltern-Konstruktoren aufrufen
        DatenbankVerbindung.__init__(self, verbindungs_string)
        CacheManager.__init__(self, cache_große)
        # Leicht einen zu vergessen, was zu nicht initialisiertem Zustand führt

Sichere Lösung

// Sicher: Komposition statt Mehrfachvererbung

// Schnittstelle für Logging
class ILogger {
public:
    virtual void log(const std::string& nachricht) = 0;
    virtual ~ILogger() = default;
};

// Schnittstelle für Netzwerksenden
class INetzwerkSender {
public:
    virtual void senden(const std::string& daten) = 0;
    virtual ~INetzwerkSender() = default;
};

// Konkrete Implementierung von Logger
class DateiLogger : public ILogger {
private:
    std::ofstream logDatei;

public:
    explicit DateiLogger(const std::string& pfad) {
        logDatei.open(pfad, std::ios::app);
    }

    void log(const std::string& nachricht) override {
        logDatei << "[LOG] " << nachricht << std::endl;
    }
};

// Konkrete Implementierung von Netzwerksender
class NetzwerkClient : public INetzwerkSender {
private:
    int socket_fd;

public:
    explicit NetzwerkClient(const std::string& server) {
        // Mit Server verbinden
    }

    void senden(const std::string& daten) override {
        // Daten senden
    }
};

// Sicher: Komposition statt Mehrfachvererbung
class SichererRemoteLogger : public ILogger {
private:
    std::unique_ptr<ILogger> lokalerLogger;
    std::unique_ptr<INetzwerkSender> netzwerkSender;

public:
    SichererRemoteLogger(
        std::unique_ptr<ILogger> logger,
        std::unique_ptr<INetzwerkSender> sender)
        : lokalerLogger(std::move(logger))
        , netzwerkSender(std::move(sender)) {}

    void log(const std::string& nachricht) override {
        // Klares, vorhersagbares Verhalten
        lokalerLogger->log(nachricht);
        netzwerkSender->senden(nachricht);
    }
};
# Sicher: Komposition statt Vererbung in Python

from abc import ABC, abstractmethod
from typing import Optional

# Schnittstelle/Protokoll für Datenbankoperationen
class DatenbankSchnittstelle(ABC):
    @abstractmethod
    def ausführen(self, abfrage: str) -> list:
        pass

# Schnittstelle für Caching
class CacheSchnittstelle(ABC):
    @abstractmethod
    def holen(self, schlüssel: str) -> Optional[any]:
        pass

    @abstractmethod
    def setzen(self, schlüssel: str, wert: any) -> None:
        pass

# Konkrete Datenbank-Implementierung
class DatenbankVerbindung(DatenbankSchnittstelle):
    def __init__(self, verbindungs_string: str):
        self.verbindungs_string = verbindungs_string
        self.verbindung = self._verbinden()

    def _verbinden(self):
        return create_connection(self.verbindungs_string)

    def ausführen(self, abfrage: str) -> list:
        return self.verbindung.execute(abfrage)

# Konkrete Cache-Implementierung
class CacheManager(CacheSchnittstelle):
    def __init__(self, max_große: int):
        self.cache = {}
        self.max_große = max_große

    def holen(self, schlüssel: str) -> Optional[any]:
        return self.cache.get(schlüssel)

    def setzen(self, schlüssel: str, wert: any) -> None:
        if len(self.cache) >= self.max_große:
            self._entfernen()
        self.cache[schlüssel] = wert

# Sicher: Komposition statt Mehrfachvererbung
class SichereGecachteDatenbank(DatenbankSchnittstelle):
    """Verwendet Komposition, um Datenbank- und Cache-Funktionalität zu kombinieren"""

    def __init__(self,
                 datenbank: DatenbankSchnittstelle,
                 cache: CacheSchnittstelle):
        self._datenbank = datenbank
        self._cache = cache

    def ausführen(self, abfrage: str) -> list:
        # Zuerst Cache prüfen
        gecacht = self._cache.holen(abfrage)
        if gecacht is not None:
            return gecacht

        # Ausführen und cachen
        ergebnis = self._datenbank.ausführen(abfrage)
        self._cache.setzen(abfrage, ergebnis)
        return ergebnis


# Verwendung mit Dependency Injection
datenbank = DatenbankVerbindung("postgresql://localhost/meinedb")
cache = CacheManager(max_große=1000)
gecachte_db = SichereGecachteDatenbank(datenbank, cache)

CVE-Beispiele

Diese CWE ist als VERBOTEN für direkte CVE-Zuordnung markiert, da sie eher ein Code-Qualitätsproblem als eine direkte Sicherheitsschwachstelle darstellt.


Referenzen

  1. MITRE Corporation. "CWE-1055: Multiple Inheritance from Concrete Classes." https://cwe.mitre.org/data/definitions/1055.html

  2. Gamma, E. et al. "Design Patterns: Elements of Reusable Object-Oriented Software."

  3. Martin, Robert C. "Clean Architecture."