UFM.FFM.MUST

Freed memory is freed again

If memory is used or referenced after it's been freed, or if it's freed twice, results can be unpredictable. Memory reference problems can cause the use of unexpected values, which in turn can cause programs to crash or execute arbitrary code.

Use-after-free or double-free memory errors typically occur in error conditions or exceptions, unresolved race conditions, or when there's confusion of responsibility for memory between different parts of the program. The UFM checkers find instances of various use-after-free and double-free memory situations in code. The UFM.FFM.MUST checker flags situations in which already freed memory has been freed again.

Vulnerability and risk

Memory reference issues can prove to be critical problems. Using previously freed memory can result in corruption of valid data or execution of arbitrary code, depending on the specific situation. When memory is freed, its contents can remain intact and accessible if it isn't reallocated or recycled. The data at the freed location may seem to be valid, but it can change unexpectedly, and cause unintended code behavior.

If memory is allocated to another pointer at some time after it's been freed and the original pointer is used again, it can point to a location in the new allocation. As the data is changed, it can corrupt the validly used memory and result in undefined actions. If a function pointer is overwritten with an address to valid code, a malicious user can cause execution of arbitrary code.

When a program frees the same memory twice, the memory management data structures become corrupted, causing the program to crash or return the same pointer in two later function calls. In this case, an attacker can achieve control over the data that's written into the doubly-allocated memory, which then becomes vulnerable to a buffer overflow attack.

Mitigation and prevention

To avoid memory reference problems:

  • Set pointers to null once they're freed.
  • Make sure global variables are freed only once.
  • Be particularly careful freeing memory in a loop or condiitonal statement, and when you're using the realloc function.
  • Ensure that clean-up routines respect the state of memory allocation.

Vulnerable code example

Copy
  #include <stdlib.h>
  
  typedef struct x {
      char * field;
  } tx;
  
  void release(tx * a){
      free(a->field);
      free(a);
 }
 
 int main() {
     tx *a = (tx *)malloc(sizeof(tx));
     if (a==NULL) return;
     a->field = (char *)malloc(10);
     release(a);
     free(a->field);
     free(a);
     return 0;
 }

Klocwork produces a report indicating that the memory pointed by 'a->field' is freed at line 18, after it was freed by a call to the 'release' function at line 16. Typically, memory is allocated and freed at points further removed in the code than in this example, making it difficult to recognize the situation. In any case, the results of freeing already freed memory can be unpredictable and often critical.