Data Element containing Pointer Item without Proper Copy Control Element
Description
Data Element containing Pointer Item without Proper Copy Control Element occurs when code contains a data element with a pointer that does not have an associated copy constructor or assignment operator method. In C++, this is known as violating the "Rule of Three" (or "Rule of Five" in modern C++): if a class manages a resource (like dynamically allocated memory via a pointer), it should define a destructor, copy constructor, and copy assignment operator. Without proper copy control, copying objects leads to shallow copies where multiple objects share the same pointer, causing double-free errors, use-after-free vulnerabilities, and memory corruption.
Risk
Missing copy control for pointer-containing objects has serious security implications. Shallow copies lead to double-free vulnerabilities when both objects are destroyed. Use-after-free conditions occur when one copy is deleted and the other continues to use the pointer. Memory corruption from these issues can be exploited for code execution. Data leaks may occur if sensitive data is not properly managed during copies. Resource exhaustion can happen if resources are not properly released. The undefined behavior from improper copying is unpredictable and potentially exploitable.
Solution
Follow the Rule of Three (C++03) or Rule of Five (C++11): if you define one of destructor, copy constructor, or copy assignment operator, define all of them. In modern C++, also consider move constructor and move assignment operator. Use smart pointers (unique_ptr, shared_ptr) to automate resource management. Apply the Rule of Zero: prefer using RAII wrappers so that special members are not needed. Use static analysis tools to detect violations. Consider making classes non-copyable if copying doesn't make sense. Use = delete to explicitly prevent copying when appropriate.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Memory Corruption - Double-free and use-after-free from improper copies. |
| Availability | Scope: Availability DoS: Crash - Memory corruption leads to crashes. |
| Confidentiality | Scope: Confidentiality Read Memory - Use-after-free may expose sensitive data. |
Example Code
Vulnerable Code
// Vulnerable: Rule of Three violation
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; // Proper cleanup
}
// Missing: Copy constructor!
// Missing: Copy assignment operator!
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("secret data", 11);
// Vulnerable: Shallow copy - both point to same memory!
VulnerableBuffer buf2 = buf1; // Uses default copy constructor
// buf1.data and buf2.data point to same memory!
// When buf2 is destroyed, data is freed
// When buf1 is destroyed, data is freed AGAIN (double-free!)
}
void vulnerableAssignment() {
VulnerableBuffer buf1(100);
VulnerableBuffer buf2(50);
buf2 = buf1; // Default assignment - shallow copy!
// buf2's original memory leaked!
// buf1 and buf2 share same pointer!
}
// Vulnerable: Class with pointer to complex object
class VulnerableDocument {
private:
std::string* content;
std::string* metadata;
User* owner; // Raw pointer to external object
public:
VulnerableDocument(const std::string& text, User* user) {
content = new std::string(text);
metadata = new std::string("{}");
owner = user;
}
~VulnerableDocument() {
delete content;
delete metadata;
// Don't delete owner - it's shared
}
// No copy constructor defined!
// No copy assignment defined!
void setContent(const std::string& text) {
*content = text;
}
};
void vulnerableDocumentCopy() {
User user("admin");
VulnerableDocument doc1("secret", &user);
// Shallow copy - both docs point to same content!
VulnerableDocument doc2 = doc1;
doc2.setContent("modified");
// BUG: doc1's content also changed!
// When function exits:
// doc2 destroyed - deletes content and metadata
// doc1 destroyed - double-free!
}
// Vulnerable: Vector of objects with pointers
class VulnerableResource {
private:
int* values;
int count;
public:
VulnerableResource(int n) : count(n) {
values = new int[n];
}
~VulnerableResource() {
delete[] values;
}
// Missing copy control!
};
void vulnerableVector() {
std::vector<VulnerableResource> resources;
VulnerableResource r(10);
resources.push_back(r); // Shallow copy into vector!
// r and resources[0] share same values pointer
// When vector reallocates or r goes out of scope:
// Double-free or use-after-free!
}
Fixed Code
// Fixed: Proper Rule of Three implementation
class FixedBuffer {
private:
char* data;
size_t size;
public:
// Constructor
explicit FixedBuffer(size_t sz) : size(sz), data(nullptr) {
if (size > 0) {
data = new char[size];
memset(data, 0, size);
}
}
// Destructor
~FixedBuffer() {
delete[] data;
}
// Copy constructor - deep copy
FixedBuffer(const FixedBuffer& other) : size(other.size), data(nullptr) {
if (size > 0) {
data = new char[size];
memcpy(data, other.data, size);
}
}
// Copy assignment operator - deep copy with proper cleanup
FixedBuffer& operator=(const FixedBuffer& other) {
if (this != &other) { // Self-assignment check
// Create new buffer first (exception safety)
char* newData = nullptr;
if (other.size > 0) {
newData = new char[other.size];
memcpy(newData, other.data, other.size);
}
// Clean up old data
delete[] data;
// Assign new data
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; }
};
// Fixed: Rule of Five (C++11) with move semantics
class ModernBuffer {
private:
char* data;
size_t size;
public:
// Constructor
explicit ModernBuffer(size_t sz) : size(sz), data(nullptr) {
if (size > 0) {
data = new char[size](); // Value-initialized
}
}
// Destructor
~ModernBuffer() {
delete[] data;
}
// Copy constructor
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 constructor
ModernBuffer(ModernBuffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// Copy assignment
ModernBuffer& operator=(const ModernBuffer& other) {
if (this != &other) {
ModernBuffer temp(other); // Copy-and-swap idiom
swap(*this, temp);
}
return *this;
}
// Move assignment
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 function for copy-and-swap
friend void swap(ModernBuffer& a, ModernBuffer& b) noexcept {
using std::swap;
swap(a.data, b.data);
swap(a.size, b.size);
}
// Accessors
const char* getData() const { return data; }
size_t getSize() const { return size; }
};
// Fixed: Rule of Zero - use smart pointers
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);
}
}
// No destructor needed - unique_ptr handles cleanup
// No copy constructor needed - unique_ptr is move-only
// If copying is needed, implement deep copy:
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 operations are automatically generated for unique_ptr
SmartBuffer(SmartBuffer&&) = default;
SmartBuffer& operator=(SmartBuffer&&) = default;
const char* getData() const { return data.get(); }
};
// Fixed: Non-copyable class when copying doesn't make sense
class NonCopyableResource {
private:
int* handle;
public:
explicit NonCopyableResource(int id) {
handle = acquireResource(id);
}
~NonCopyableResource() {
releaseResource(handle);
}
// Explicitly delete copy operations
NonCopyableResource(const NonCopyableResource&) = delete;
NonCopyableResource& operator=(const NonCopyableResource&) = delete;
// Allow move operations
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; }
};
// Safe usage in vector with move semantics
void safeVector() {
std::vector<NonCopyableResource> resources;
resources.push_back(NonCopyableResource(1)); // Move into vector
resources.emplace_back(2); // Construct in place
// No double-free - each resource has unique ownership
}
// Fixed: Document with proper copy semantics
class FixedDocument {
private:
std::unique_ptr<std::string> content;
std::unique_ptr<std::string> metadata;
std::shared_ptr<User> owner; // Shared ownership
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)) {}
// Deep copy
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 - shared ownership
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;
}
// Default move operations work correctly with smart pointers
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("secret", user);
// Deep copy - independent content
FixedDocument doc2 = doc1;
doc2.setContent("modified");
// doc1's content unchanged - "secret"
// doc2's content is "modified"
// Both share the same User through shared_ptr
// No double-free issues
}
CVE Examples
Double-free and use-after-free vulnerabilities from missing copy control have been common sources of exploitable memory corruption bugs in C++ applications.
Related CWEs
- CWE-1076: Insufficient Adherence to Expected Conventions (parent)
- CWE-415: Double Free (can result from)
- CWE-416: Use After Free (can result from)
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer (related)
References
- 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.