CWE-463 is the accidental deletion of a data structure sentinel, which can cause serious programming logic problems. A sentinel is a special value that marks the structure of data. MITRE's examples are the null character at the end of a C string and an end marker in a linked list.
It applies to C and C++ code that manipulates raw buffers.
How it happens
Sentinels live inside the same memory that ordinary data does, so a stray write can erase one. MITRE's example allocates room for a string and fills it:
char *foo;
int counter;
foo = calloc(sizeof(char) * 10);
for (counter = 0; counter != 10; counter++) {
foo[counter] = 'a';
printf("%s\n", foo);
}
The buffer has space for 9 characters plus a null terminator, but 10 characters are written. The terminator is overwritten, so printf has nothing marking the end of the string, and the result is unpredictable and possibly dangerous.
MITRE stresses that control data like this is dangerous when it is easily accessible, and should be protected from deletion or modification outside a wrapper interface that provides safety.
What an attacker gains
From the record: availability impact, because the structure generally stops working properly, and authorization impact. If a control character such as NULL is removed, resource access control problems can follow.
How to find it
The record lists no detection methods. Look for loops that write up to and including the buffer size, and for code that writes a fixed number of bytes into a string. Compile with overflow detection and test boundary sizes.
How to fix it
MITRE's mitigations, none of them complete on their own:
- Use an abstraction library to abstract away risky APIs.
- Compile with features that automatically detect buffer overflows, such as the Visual Studio /GS flag, the FORTIFY_SOURCE GCC flag, StackGuard or ProPolice. These can only detect certain overflows, and the typical response is to exit, so a denial of service remains possible.
- Use OS-level preventative functionality.
The direct fix is to size the buffer for the data plus the terminator and bound the loop:
char *foo = calloc(10, sizeof(char)); /* 9 chars + terminator */
for (int i = 0; i < 9; i++) foo[i] = 'a';
foo[9] = '\0';
Quick check
Why is a missing null terminator a security issue?
String functions read until they find one. Without it they read past the buffer, so results are unpredictable.