CWE-1087, Class with Virtual Method without a Virtual Destructor, describes a class that declares a virtual method but has no associated virtual destructor. It is tagged for object oriented languages, and C++ is where it bites.
It is a close cousin of CWE-1079, which covers a parent class with children but no virtual destructor. The signal here is different: a virtual method is a strong hint that the class is designed to be used polymorphically, which means someone will eventually delete a derived object through a base pointer. MITRE allows this entry for mapping real world vulnerabilities, after careful reading of its description.
How it happens
The rule in C++ is simple. If a class has virtual functions, objects will be handled through base class pointers or references, and deletion through that pointer needs a virtual destructor to reach the derived destructor.
// Vulnerable pattern
class Codec {
public:
virtual void decode(const char* data) = 0;
~Codec() {} // not virtual
};
class Mp3Codec : public Codec {
unsigned char* frame = new unsigned char[8192];
public:
void decode(const char* data) override { /* ... */ }
~Mp3Codec() { delete[] frame; }
};
void run() {
Codec* c = new Mp3Codec();
c->decode("...");
delete c; // undefined behavior, Mp3Codec::~Mp3Codec skipped
}
The compiler accepts this without complaint on default settings. The program may even appear to work, because undefined behavior includes behaving normally on your machine.
// Fix
class Codec {
public:
virtual void decode(const char* data) = 0;
virtual ~Codec() = default;
};
What an attacker gains
MITRE lists reduced reliability, for example undefined behavior, and notes that if an attacker can reach the code, the reliability problem might become a vulnerability. Think of a network service that creates and destroys a polymorphic object per request. A leak in the derived destructor, hit on every request, becomes a memory exhaustion path. Undefined behavior can also corrupt memory in ways that are hard to predict.
How to find it
MITRE names automated static analysis as a detection method. Beyond that:
- Enable the compiler warning for deleting an object of a class with virtual functions through a base pointer with a non virtual destructor. GCC and Clang both offer one.
- Run a static analyzer in CI and treat the finding as an error.
- Run tests under a memory checker such as AddressSanitizer, which reports leaks and some invalid deletes.
- Grep new classes for the keyword
virtualand check that a destructor is declared.
How to fix it
The MITRE record lists no formal mitigations, so these are standard C++ practice.
- Declare a virtual destructor in every class that has virtual methods and is meant as a base.
- Use
= defaultwhen the destructor has nothing to do. It costs nothing and documents intent. - If the class should not be deleted through a base pointer at all, make the base destructor protected and non virtual instead.
- Mark classes that are not meant to be inherited from as final.
- Hold polymorphic objects in a smart pointer so raw delete calls are rare. This reduces exposure, but it does not replace a correct class design.
What a good review looks like
When you read a pull request that adds a class hierarchy, ask three questions. Is there a virtual method? Is there a destructor on the base? Is it virtual or protected? Those three checks take seconds and prevent a class of bug that is otherwise silent until production load shows up the leak.
Quick check
Why does a virtual method imply a virtual destructor?
A virtual method means the class is meant for polymorphic use. Objects will be deleted through base pointers, and only a virtual destructor makes the derived destructor run.
What is the alternative to a virtual destructor?
A protected non virtual destructor, which stops callers from deleting through the base pointer at all. Pick one approach deliberately.