CL.FFM.ASSIGN
Freeing freed memory due to missing operator
Class-level checkers produce recommendations based on Scott Meyer's rules for effective C++ class construction.
CL.FFM.ASSIGN is based on Scott Meyer's Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory. This checker looks for classes that contain dynamically allocated data members and do not define an assignment operator.
When there is no implementation of the assignment operator, the C++ compiler auto-generates an assignment operator wherever it is needed, but that compiler-provided implementation is always explicitly shallow.
Copying an object can result in two objects referencing a single dynamically allocated data member if the copy is not explicitly coded to manage those data members. A shallow copy operation in which pointers are simply copied by value results in a pair of objects both pointing to the same underlying heap memory. Any operation performed on that heap memory will affect both objects that are maintaining references to it, which can lead to unexpected results in all types of program.
The situation this particular checker references is the potential for freeing already freed memory, which occurs when two such objects sharing a common underlying allocation go out of scope.
Vulnerability and risk
In this situation, the first object to go out of scope will typically release all associated heap memory, including the buffer that is now shared with another object. When that second object goes out of scope, its attempt to free what it believes to be its own memory resources will result in access to already released memory, something that could corrupt the heap in a worst-case scenario.
Mitigation and prevention
To address the problem, always provide an explicit implementation of the assignment operator for classes that contain dynamically allocated data members, and always ensure that the assignment operator performs a deep copy of those data members.
Vulnerable code example
#include <iostream>
using namespace std;
class C{
char *data;
C(const C&){}
public:
C(){ data = new char[10]; }
~C() {
cout << "Calling delete for " << (void *)data << endl;
delete[] data;
}
};
int main(){
C c1;
C c2;
c1 = c2;
return 1;
}
Output:
Calling delete for 0x602030
Calling delete for 0x602030
In this example class 'C' uses dynamic memory allocation for 'C::data', but doesn't define operator=. As a result, when line 16 of function 'main()' is executed, both 'c1.data' and 'c2.data' have the same value and each calls operator 'delete[ ]' when destroyed, the same pointer being deleted twice. In this case, CL.FFM.ASSIGN has found a typical example of freed memory being freed again due to the operator= implementation.
Fixed code example
In order to fix this problem, operator= should be defined. Depending on the situation, different implementations of operator= can be used.
- If it's necessary to allocate new memory, the operator= implementation should check that it doesn't copy to itself, release old memory, allocate new memory, and copy the data:
class C{
// ...
C& operator=(const C& src){
if (&src == this) return *this;
delete[] data;
data = new char[10];
memcpy(data, src.data, 10);
return *this;
}
// ...
};
- The previous implementation performs unnecessary heap memory operations, since the destroyed data and the allocated data have the same size. In such cases, the old memory can be reused:
class C{
// ...
C& operator=(const C& src){
if (&src == this) return *this;
memcpy(data, src.data, 10);
return *this;
}
// ...
};
- If it's not expected that class instances are copied, the operator= should be declared as private. In this case, if an attempt to copy is made, the compiler produces an error.
class C{
// ...
private:
C& operator=(const C&){ return *this;}
// ...
};
Related checkers
External guidance
Security training
Application security training materials provided by Secure Code Warrior.
Extension
This checker can be extended through the Klocwork knowledge base. See Tuning C/C++ analysis for more information.