The C Standard, Annex J (184) [ISO/IEC 9899:2024], states that the behavior of a program is undefined when
The pointer argument to the
free
orrealloc
function does not match a pointer earlier returned by a memory management function, or the space has been deallocated by a call tofree
orrealloc
.
See also undefined behavior 179.
Freeing memory that is not allocated dynamically can cause a serious error. The specifics and extent of damage caused by this error depends on the compiler in use, but can range from nothing to unintended and unexpected program termination. Regardless of the implementation, calling free() on non-dynamic memory should be avoidedresult in heap corruption and other serious errors. Do not call free()
on a pointer other than one returned by a standard memory allocation function, such as malloc()
, calloc()
, realloc()
, or aligned_alloc()
.
A similar situation arises when realloc()
is supplied a pointer to non-dynamically allocated memory. The realloc()
function is used to resize a block of dynamic memory. If realloc()
is supplied a pointer to memory not allocated by another dynamic allocation routine, such as malloc(), a series error could occur.
Non-compliant Code Example 1
In this example, a file is opened for reading. If the file is opened successfully, then a block of memory is allocated on the heap with malloc() and pointed to by str. A message indicating that the file was opened properly is copied into a block of dynamic memory pointed to by str, and the message is printed via the log_it() function. After log_it() is called, the dynamic memory is freed. However, if the file does not open correctly, then str will be set to a non-dynamic string. When this str is freed, since it points to non-dynamic memory, an error will occur.
a standard memory allocation function, the behavior is undefined. One consequence is that the program may terminate abnormally.
This rule does not apply to null pointers. The C Standard guarantees that if free()
is passed a null pointer, no action occurs.
Noncompliant Code Example
This noncompliant code example sets c_str
to reference either dynamically allocated memory or a statically allocated string literal depending on the value of argc
. In either case, c_str
is passed as an argument to free()
. If anything other than dynamically allocated memory is referenced by c_str
, the call to free(c_str)
is erroneous.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
#include <string | ||||
Code Block | ||||
#include <stdlib.h> #include <stdio.h> void log_it(char *msg_buf) { printf("LOG: %s\n", msg_buf); }enum { MAX_ALLOCATION = 1000 }; int main(void) { FILE *file = NULL;int argc, const char *argv[]) { char *c_str = NULL, *fname="~/config_file"; size_t size len; if (argc = 100; = 2) { filelen = fopen("~/config_file","r")strlen(argv[1]) + 1; if (filelen != NULL> MAX_ALLOCATION) { /* Handle error */ } c_str = (char *)malloc(sizelen); if snprintf(str,size,"File %s opened properly",fname);c_str == NULL) { /* Handle error */ } logstrcpy(c_it(strstr, argv[1]); } else { c_str = "FILE OPENEDusage: $>a.exe [string]"; log_it(printf("%s\n", c_str); } free(c_str); return 0; } |
Compliant
...
Solution
This compliant example is very similar to the non-compliant example above. The only modification is the call to free has been moved inside the conditional statement to ensure that only dynamic memory is freed.solution eliminates the possibility of c_str
referencing memory that is not allocated dynamically when passed to free()
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
#include <string | ||||
Code Block | ||||
#include <stdlib.h> #include <stdio.h> void log_it(char *msg_buf) { printf("LOG: %s\n", msg_buf); }enum { MAX_ALLOCATION = 1000 }; int main(void) { FILE *file = NULL;int argc, const char *argv[]) { char *c_str = NULL, *fname="~/config_file"; size_t sizelen; if (argc = 100; = 2) { filelen = fopen("~/config_file","r")strlen(argv[1]) + 1; if (filelen != NULL> MAX_ALLOCATION) { /* Handle error */ } c_str = (char *)malloc(sizelen); snprintfif (c_str,size,"File %s opened properly",fname); == NULL) { log_it(str); free(str); /* Handle error */ /* only} dynamic memory is freed */ strcpy(c_str, argv[1]); } else { str = "FILE OPENED"printf("%s\n", "usage: $>a.exe [string]"); log_it(str)return EXIT_FAILURE; } free(c_str); return 0; } |
...
Noncompliant Code Example
...
This example attempts to resize the string referenced by buf to make enough room in buf to append another string, line. However, once in the function append, there is no way to determine how buf was allocated. When realloc() is called on buf, since buf does not point to dynamic memory, an error may occur.
(realloc()
)
In this noncompliant example, the pointer parameter to realloc()
, buf
, does not refer to dynamically allocated memory:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
enum { BUFSIZE = 256 };
void f(void | ||||
Code Block | ||||
void append(char *buf, size_t count, size_t size) { char *line = " <- THIS IS A LINE"buf[BUFSIZE]; int line_lenchar *p = strlen(line); if ((count + line_len) > size) { buf = realloc(buf,count+line_len);char *)realloc(buf, 2 * BUFSIZE); if (p == NULL) { size/* =Handle count + line_len;error */ } strncat(buf,line,line_len); } int main(void) { append("AAAAA",5,6); return 0; } |
Compliant Code Example 2
Correcting the above example is an exercise in documentation. Since realloc is used to resize the memory pointed to by buf, the function append has the precondition that buf must point to dynamically allocated memory.
}
|
Compliant Solution (realloc()
)
In this compliant solution, buf
refers to dynamically allocated memory:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
enum { BUFSIZE = 256 };
void f(void | ||||
Code Block | ||||
/* NOTE: buf must point to dynamically allocated memory */ void append(char *buf, size_t count, size_t size) { char *linebuf = " <- THIS IS A LINE"(char *)malloc(BUFSIZE * sizeof(char)); int line_lenchar *p = strlen(line); if ((count + line_len) > size) { buf = realloc(buf,count+line_len);char *)realloc(buf, 2 * BUFSIZE); if (p == NULL) { size/* =Handle count + line_len;error */ } strncat(buf,line,line_len); } int main(void) { size_t size = 6, count = 5; char *str = malloc(size); strncpy(str,"AAAAA", size); append(str,size,count); free(str); return 0; } |
Compliant Code Example 2A
Alternatively, the function append could be rewritten not to use realloc() to resize buf. This solution goes beyond the scope of this document, but is nonetheless viable and, depending on the context of the program, may be preferred.
} |
Note that realloc()
will behave properly even if malloc()
failed, because when given a null pointer, realloc()
behaves like a call to malloc()
.
Risk Assessment
The consequences of this error depend on the implementation, but they range from nothing to arbitrary code execution if that memory is reused by malloc()
.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MEM34-C | High | Likely | Medium | P18 | L1 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| invalid-free | Fully checked | ||||||
Axivion Bauhaus Suite |
| CertC-MEM34 | Can detect memory deallocations for stack objects | ||||||
Clang |
| clang-analyzer-unix.Malloc | Checked by clang-tidy ; can detect some instances of this rule, but does not detect all | ||||||
CodeSonar |
| ALLOC.TM | Type Mismatch | ||||||
Compass/ROSE | Can detect some violations of this rule | ||||||||
| BAD_FREE | Identifies calls to | |||||||
Cppcheck |
| autovarInvalidDeallocation mismatchAllocDealloc | Partially implemented | ||||||
Cppcheck Premium |
| autovarInvalidDeallocation mismatchAllocDealloc | Partially implemented | ||||||
Helix QAC |
| DF2721, DF2722, DF2723 | |||||||
Klocwork |
| FNH.MIGHT FNH.MUST | |||||||
LDRA tool suite |
| 407 S, 483 S, 644 S, 645 S, 125 D | Partially implemented | ||||||
Parasoft C/C++test |
| CERT_C-MEM34-a | Do not free resources using invalid pointers | ||||||
Parasoft Insure++ | Runtime analysis | ||||||||
PC-lint Plus |
| 424, 673 | Fully supported | ||||||
Polyspace Bug Finder |
| Checks for:
Rule fully covered. | |||||||
PVS-Studio |
| V585, V726 | |||||||
RuleChecker |
| invalid-free | Partially checked | ||||||
TrustInSoft Analyzer |
| unclassified ("free expects a free-able address") | Exhaustively verified (see one compliant and one non-compliant example). |
Related Vulnerabilities
CVE-2015-0240 describes a vulnerability in which an uninitialized pointer is passed to TALLOC_FREE()
, which is a Samba-specific memory deallocation macro that wraps the talloc_free()
function. The implementation of talloc_free()
would access the uninitialized pointer, resulting in a remote exploit.
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT C Secure Coding Standard | MEM31-C. Free dynamically allocated memory when no longer needed | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C | MEM51-CPP. Properly deallocate dynamically allocated resources | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961 | Reallocating or freeing memory that was not dynamically allocated [xfree] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-590, Free of Memory Not on the Heap | 2017-07-10: CERT: Exact |
Bibliography
[ISO/IEC 9899:2024] | Subclause J.2, "Undefined Behavior" |
[Seacord 2013b] | Chapter 4, "Dynamic Memory Management" |
...
j1This violates recommendation 1 (sort of)