Datenelement mit Pointer ohne ordnungsgemäßes Kopier-Kontrollelement
Beschreibung
Datenelement mit Pointer ohne ordnungsgemäßes Kopier-Kontrollelement tritt auf, wenn Code ein Datenelement mit einem Pointer enthält, das keinen zugehörigen Kopierkonstruktor oder Zuweisungsoperator hat. In C++ ist dies als Verletzung der "Rule of Three" (oder "Rule of Five" in modernem C++) bekannt: Wenn eine Klasse eine Ressource verwaltet (wie dynamisch allokierten Speicher über einen Pointer), sollte sie einen Destruktor, Kopierkonstruktor und Kopierzuweisungsoperator definieren. Ohne ordnungsgemäße Kopierkontrolle führt das Kopieren von Objekten zu flachen Kopien, bei denen mehrere Objekte den gleichen Pointer teilen, was Double-Free-Fehler, Use-After-Free-Schwachstellen und Speicherkorruption verursacht.
Risiko
Fehlende Kopierkontrolle für Objekte mit Zeigern hat ernsthafte Sicherheitsimplikationen. Flache Kopien führen zu Double-Free-Schwachstellen, wenn beide Objekte zerstört werden. Use-After-Free-Bedingungen treten auf, wenn eine Kopie gelöscht wird und die andere den Pointer weiterhin verwendet. Speicherkorruption durch diese Probleme kann für Code-Ausführung ausgenutzt werden. Datenlecks können auftreten, wenn sensible Daten beim Kopieren nicht ordnungsgemäß verwaltet werden. Ressourcenerschöpfung kann entstehen, wenn Ressourcen nicht ordnungsgemäß freigegeben werden. Das undefinierte Verhalten durch fehlerhaftes Kopieren ist unvorhersehbar und potenziell ausnutzbar.
Lösung
Befolgen Sie die Rule of Three (C++03) oder Rule of Five (C++11): Wenn Sie einen von Destruktor, Kopierkonstruktor oder Kopierzuweisungsoperator definieren, definieren Sie alle. In modernem C++ berücksichtigen Sie auch Move-Konstruktor und Move-Zuweisungsoperator. Verwenden Sie Smart Pointer (unique_ptr, shared_ptr), um Ressourcenverwaltung zu automatisieren. Wenden Sie die Rule of Zero an: Bevorzugen Sie RAII-Wrapper, sodass spezielle Mitglieder nicht benötigt werden. Verwenden Sie statische Analysetools, um Verletzungen zu erkennen. Erwägen Sie, Klassen nicht kopierbar zu machen, wenn Kopieren keinen Sinn macht. Verwenden Sie = delete, um Kopieren explizit zu verhindern, wenn angemessen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Speicherkorruption - Double-Free und Use-After-Free durch fehlerhafte Kopien. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz - Speicherkorruption führt zu Abstürzen. |
| Vertraulichkeit | Bereich: Vertraulichkeit Speicher lesen - Use-After-Free kann sensible Daten preisgeben. |
Beispielcode
Anfälliger Code
// Anfällig: Rule of Three-Verletzung
class VulnerableBuffer {
private:
char* data;
size_t size;
public:
VulnerableBuffer(size_t sz) : size(sz) {
data = new char[size];
memset(data, 0, size);
}
~VulnerableBuffer() {
delete[] data; // Ordnungsgemäße Bereinigung
}
// Fehlt: Kopierkonstruktor!
// Fehlt: Kopierzuweisungsoperator!
void write(const char* input, size_t len) {
if (len <= size) {
memcpy(data, input, len);
}
}
char* getData() { return data; }
};
void vulnerableUsage() {
VulnerableBuffer buf1(100);
buf1.write("geheime daten", 13);
// Anfällig: Flache Kopie - beide zeigen auf gleichen Speicher!
VulnerableBuffer buf2 = buf1; // Verwendet Standard-Kopierkonstruktor
// buf1.data und buf2.data zeigen auf gleichen Speicher!
// Wenn buf2 zerstört wird, wird data freigegeben
// Wenn buf1 zerstört wird, wird data ERNEUT freigegeben (Double-Free!)
}
void vulnerableAssignment() {
VulnerableBuffer buf1(100);
VulnerableBuffer buf2(50);
buf2 = buf1; // Standard-Zuweisung - flache Kopie!
// buf2s ursprünglicher Speicher ist verloren!
// buf1 und buf2 teilen den gleichen Pointer!
}
// Anfällig: Klasse mit Pointer auf komplexes Objekt
class VulnerableDocument {
private:
std::string* content;
std::string* metadata;
User* owner; // Roher Pointer auf externes Objekt
public:
VulnerableDocument(const std::string& text, User* user) {
content = new std::string(text);
metadata = new std::string("{}");
owner = user;
}
~VulnerableDocument() {
delete content;
delete metadata;
// owner nicht löschen - wird geteilt
}
// Kein Kopierkonstruktor definiert!
// Keine Kopierzuweisung definiert!
void setContent(const std::string& text) {
*content = text;
}
};
void vulnerableDocumentCopy() {
User user("admin");
VulnerableDocument doc1("geheim", &user);
// Flache Kopie - beide Dokumente zeigen auf gleichen Inhalt!
VulnerableDocument doc2 = doc1;
doc2.setContent("modifiziert");
// FEHLER: doc1s Inhalt wurde auch geändert!
// Wenn Funktion endet:
// doc2 zerstört - löscht content und metadata
// doc1 zerstört - Double-Free!
}
// Anfällig: Vektor von Objekten mit Zeigern
class VulnerableResource {
private:
int* values;
int count;
public:
VulnerableResource(int n) : count(n) {
values = new int[n];
}
~VulnerableResource() {
delete[] values;
}
// Fehlende Kopierkontrolle!
};
void vulnerableVector() {
std::vector<VulnerableResource> resources;
VulnerableResource r(10);
resources.push_back(r); // Flache Kopie in Vektor!
// r und resources[0] teilen den gleichen values-Pointer
// Wenn Vektor realloziiert oder r den Gültigkeitsbereich verlässt:
// Double-Free oder Use-After-Free!
}
Korrigierter Code
// Korrigiert: Ordnungsgemäße Rule of Three-Implementierung
class FixedBuffer {
private:
char* data;
size_t size;
public:
// Konstruktor
explicit FixedBuffer(size_t sz) : size(sz), data(nullptr) {
if (size > 0) {
data = new char[size];
memset(data, 0, size);
}
}
// Destruktor
~FixedBuffer() {
delete[] data;
}
// Kopierkonstruktor - tiefe Kopie
FixedBuffer(const FixedBuffer& other) : size(other.size), data(nullptr) {
if (size > 0) {
data = new char[size];
memcpy(data, other.data, size);
}
}
// Kopierzuweisungsoperator - tiefe Kopie mit ordnungsgemäßer Bereinigung
FixedBuffer& operator=(const FixedBuffer& other) {
if (this != &other) { // Selbstzuweisungsprüfung
// Zuerst neuen Puffer erstellen (Ausnahmesicherheit)
char* newData = nullptr;
if (other.size > 0) {
newData = new char[other.size];
memcpy(newData, other.data, other.size);
}
// Alte Daten bereinigen
delete[] data;
// Neue Daten zuweisen
data = newData;
size = other.size;
}
return *this;
}
void write(const char* input, size_t len) {
if (data && len <= size) {
memcpy(data, input, len);
}
}
const char* getData() const { return data; }
size_t getSize() const { return size; }
};
// Korrigiert: Rule of Five (C++11) mit Move-Semantik
class ModernBuffer {
private:
char* data;
size_t size;
public:
// Konstruktor
explicit ModernBuffer(size_t sz) : size(sz), data(nullptr) {
if (size > 0) {
data = new char[size](); // Wert-initialisiert
}
}
// Destruktor
~ModernBuffer() {
delete[] data;
}
// Kopierkonstruktor
ModernBuffer(const ModernBuffer& other) : size(other.size), data(nullptr) {
if (size > 0) {
data = new char[size];
std::copy(other.data, other.data + size, data);
}
}
// Move-Konstruktor
ModernBuffer(ModernBuffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Kopierzuweisung
ModernBuffer& operator=(const ModernBuffer& other) {
if (this != &other) {
ModernBuffer temp(other); // Copy-and-Swap-Idiom
swap(*this, temp);
}
return *this;
}
// Move-Zuweisung
ModernBuffer& operator=(ModernBuffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
// Swap-Funktion für Copy-and-Swap
friend void swap(ModernBuffer& a, ModernBuffer& b) noexcept {
using std::swap;
swap(a.data, b.data);
swap(a.size, b.size);
}
// Zugriffsmethoden
const char* getData() const { return data; }
size_t getSize() const { return size; }
};
// Korrigiert: Rule of Zero - Smart Pointer verwenden
class SmartBuffer {
private:
std::unique_ptr<char[]> data;
size_t size;
public:
explicit SmartBuffer(size_t sz) : size(sz) {
if (size > 0) {
data = std::make_unique<char[]>(size);
}
}
// Kein Destruktor benötigt - unique_ptr übernimmt Bereinigung
// Kein Kopierkonstruktor benötigt - unique_ptr ist move-only
// Wenn Kopieren benötigt wird, tiefe Kopie implementieren:
SmartBuffer(const SmartBuffer& other) : size(other.size) {
if (size > 0) {
data = std::make_unique<char[]>(size);
std::copy(other.data.get(), other.data.get() + size, data.get());
}
}
SmartBuffer& operator=(const SmartBuffer& other) {
if (this != &other) {
size = other.size;
if (size > 0) {
data = std::make_unique<char[]>(size);
std::copy(other.data.get(), other.data.get() + size, data.get());
} else {
data.reset();
}
}
return *this;
}
// Move-Operationen werden automatisch für unique_ptr generiert
SmartBuffer(SmartBuffer&&) = default;
SmartBuffer& operator=(SmartBuffer&&) = default;
const char* getData() const { return data.get(); }
};
// Korrigiert: Nicht kopierbare Klasse, wenn Kopieren keinen Sinn macht
class NonCopyableResource {
private:
int* handle;
public:
explicit NonCopyableResource(int id) {
handle = acquireResource(id);
}
~NonCopyableResource() {
releaseResource(handle);
}
// Kopieroperationen explizit löschen
NonCopyableResource(const NonCopyableResource&) = delete;
NonCopyableResource& operator=(const NonCopyableResource&) = delete;
// Move-Operationen erlauben
NonCopyableResource(NonCopyableResource&& other) noexcept
: handle(other.handle) {
other.handle = nullptr;
}
NonCopyableResource& operator=(NonCopyableResource&& other) noexcept {
if (this != &other) {
releaseResource(handle);
handle = other.handle;
other.handle = nullptr;
}
return *this;
}
int* getHandle() const { return handle; }
};
// Sichere Verwendung in Vektor mit Move-Semantik
void safeVector() {
std::vector<NonCopyableResource> resources;
resources.push_back(NonCopyableResource(1)); // Move in Vektor
resources.emplace_back(2); // An Ort und Stelle konstruieren
// Kein Double-Free - jede Ressource hat eindeutige Eigentümerschaft
}
// Korrigiert: Dokument mit ordnungsgemäßer Kopiersemantik
class FixedDocument {
private:
std::unique_ptr<std::string> content;
std::unique_ptr<std::string> metadata;
std::shared_ptr<User> owner; // Geteilte Eigentümerschaft
public:
FixedDocument(const std::string& text, std::shared_ptr<User> user)
: content(std::make_unique<std::string>(text)),
metadata(std::make_unique<std::string>("{}")),
owner(std::move(user)) {}
// Tiefe Kopie
FixedDocument(const FixedDocument& other)
: content(std::make_unique<std::string>(*other.content)),
metadata(std::make_unique<std::string>(*other.metadata)),
owner(other.owner) {} // Shared Pointer - geteilte Eigentümerschaft
FixedDocument& operator=(const FixedDocument& other) {
if (this != &other) {
content = std::make_unique<std::string>(*other.content);
metadata = std::make_unique<std::string>(*other.metadata);
owner = other.owner;
}
return *this;
}
// Standard-Move-Operationen funktionieren korrekt mit Smart Pointern
FixedDocument(FixedDocument&&) = default;
FixedDocument& operator=(FixedDocument&&) = default;
void setContent(const std::string& text) {
*content = text;
}
const std::string& getContent() const { return *content; }
};
void safeDocumentCopy() {
auto user = std::make_shared<User>("admin");
FixedDocument doc1("geheim", user);
// Tiefe Kopie - unabhängiger Inhalt
FixedDocument doc2 = doc1;
doc2.setContent("modifiziert");
// doc1s Inhalt unverändert - "geheim"
// doc2s Inhalt ist "modifiziert"
// Beide teilen denselben User durch shared_ptr
// Keine Double-Free-Probleme
}
CVE-Beispiele
Double-Free- und Use-After-Free-Schwachstellen durch fehlende Kopierkontrolle waren häufige Quellen ausnutzbarer Speicherkorruptionsfehler in C++-Anwendungen.
Verwandte CWEs
- CWE-1076: Insufficient Adherence to Expected Conventions (Eltern)
- CWE-415: Double Free (kann resultieren aus)
- CWE-416: Use After Free (kann resultieren aus)
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer (verwandt)
Referenzen
- MITRE Corporation. "CWE-1098: Data Element containing Pointer Item without Proper Copy Control Element." https://cwe.mitre.org/data/definitions/1098.html
- Stroustrup, Bjarne. "The C++ Programming Language" - Rule of Three/Five/Zero.
- C++ Core Guidelines. C.21: If you define or =delete any default operation, define or =delete them all.