CWE-170 is the failure to terminate a string or array with a null character or equivalent terminator, or terminating it in the wrong place. It is a C and C++ problem at heart, because those languages mark the end of a string with a NUL byte instead of storing a length.
Anyone writing or maintaining native code, including the native parts of language runtimes and libraries, is exposed. If a function you call expects a terminated string, the terminator is part of its contract.
How it happens
MITRE says null termination errors occur in two main ways. An off-by-one error can write a null out of bounds, which causes an overflow. Or a program can use a function such as strncpy incorrectly, which prevents a terminator from being added at all.
The record's own example reads a configuration file into a buffer and then copies it with strcpy, assuming the data ends in a NUL byte.
#define MAXLEN 1024
...
char pathbuf[MAXLEN];
char inputbuf[MAXLEN];
read(cfgfile, inputbuf, MAXLEN); /* does not null terminate */
strcpy(pathbuf, inputbuf); /* requires null terminated input */
If the data on disk is terminated as expected, the code works. If an attacker changes the input so that no NUL byte is present in the first MAXLEN bytes, strcpy keeps copying from memory past the end of the buffer until it finds a zero somewhere. The copy overruns the destination.
The strncpy trap is similar. strncpy does not add a terminator when the source is as long as the limit.
char name[16];
strncpy(name, user_input, sizeof(name)); /* no terminator if input is 16 or more bytes */
printf("%s\n", name); /* reads past the buffer */
Observed examples show several routes to the same state. CVE-2000-0312 is an attacker who does not null terminate argv entries when invoking another program. CVE-2003-0777 is an interrupted step that results in a lack of null termination. CVE-2004-1072 is a fault that results in lack of null termination, leading to buffer expansion. CVE-2001-1389 is listed as multiple vulnerabilities related to improper null termination.
Notice that two of those are about error paths. An interruption or a fault skips the step that would have written the terminator.
What an attacker gains
MITRE ties several impacts to this weakness.
- An omitted terminator is the most dangerous case. It almost certainly leads to information disclosure, since string functions keep reading until they find a null, possibly well outside the intended buffer. It may also lead to a buffer overflow that can be exploited to run code.
- The same omission can crash the program or burn CPU and memory as functions run long.
- A misplaced null can overwrite valid data, which is a subset of buffer overflow and write-what-where conditions, and can corrupt data or crash the process.
- If a misplaced null changes a flag that controls access, or alters process flow, logic errors can follow and in the worst case arbitrary code execution.
How to find it
The MITRE page rates automated static analysis, usually called SAST, as able to find some instances of this weakness by building a model of data and control flow and looking for risky patterns between sources of input and sinks.
Add these checks.
- Search for strcpy, strcat, sprintf and similar calls fed by buffers that came from read, recv, fread or other calls that do not add a terminator.
- Search for strncpy, strncat and similar calls and check what happens when input is as long as the limit.
- Check every place a buffer is terminated by hand, looking for writes at index size instead of size minus one.
- Test with input exactly at the limit, one byte under and one byte over.
- Run native code under a memory error detector in tests, so reads past the end become failures.
How to fix it
The mitigations MITRE lists are plain.
- Use a language that is not susceptible to these issues. MITRE adds a caution: be careful about null byte interaction errors with lower level constructs that may be written in a susceptible language. See CWE-626.
- Understand exactly how every string function you use handles terminators. Read what it does when the source is as long as the limit.
- Watch for off-by-one errors when you append nulls to the end of strings.
- Switch to bounded string manipulation functions, and make termination explicit.
char name[16];
snprintf(name, sizeof(name), "%s", user_input); /* always terminated */
or, when you must use strncpy:
strncpy(name, user_input, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
- Track the length you actually read and use it. After read, add the terminator yourself at the position you read to, and make sure the buffer has room for it.
- MITRE also mentions adding code that validates null termination of string buffers if performance allows, but calls it a naive and error prone solution. Prefer to get termination right at the source.
- Handle error paths. Whatever step writes the terminator must run on every path, including interrupted reads and failures.
Try it yourself
Run a static scan over a small C sample and see which calls get flagged for unsafe string handling.
Click through it at your own pace, or open it full screen.
Libraries and native code you ship
Much native string handling lives in libraries that other projects depend on. If you pull one in, you inherit its termination bugs. The free in-browser dependency scan takes a manifest or lockfile with up to 100 dependencies, needs no login, and keeps your file on your machine. Reachability analysis then helps you see whether your code actually calls the vulnerable function.
Quick check
Why is a missing terminator worse than a misplaced one?
MITRE calls the omitted terminator the most dangerous case. String functions read on until they find a zero, which discloses memory and can overflow a destination buffer.
Does strncpy make copying safe?
Not by itself. It does not add a terminator when the source is as long as the limit, so you must terminate the buffer yourself.
Can this cause problems without any overflow?
Yes. A misplaced null can corrupt data or flip a flag that controls access, which MITRE says may lead to logic errors.