CWE-908, Use of Uninitialized Resource, is when a product uses or accesses a resource that has not been initialized. It is the usage side of the coin. Its sibling CWE-909 is the missing initialization that makes the use possible. If you write C or C++, or any code that manages buffers by hand, you will meet it.
How it happens
MITRE says an uninitialized resource can make the product behave unexpectedly. It may crash, access invalid memory, or do something else depending on the resource and how it is used.
Three common causes show up in code review.
A stack variable declared and read before being assigned:
int get_limit(const char *name) {
int limit;
if (lookup(name, &limit) == 0) {
/* found */
}
return limit; /* garbage when lookup failed */
}
A buffer allocated and sent without clearing:
char *buf = malloc(256);
snprintf(buf, 8, "ok");
send(sock, buf, 256, 0); /* sends 248 bytes of old heap contents */
An object used before its construction finished. CVE-2019-9805 (details) is a chain where the packet client was created before initialization was complete, resulting in a read from uninitialized memory and then memory corruption.
The other examples in the record:
- CVE-2008-4197: use of uninitialized memory may allow code execution.
- CVE-2008-2934: freeing an uninitialized pointer leads to a crash and possible code execution.
- CVE-2008-0063: the product did not clear memory contents when generating an error message, leaking information.
What an attacker gains
Two consequences are listed:
- Confidentiality: when a resource such as memory or a program variable is reused, the original contents may not be cleared before it goes to an untrusted party. This is how error messages and network buffers end up leaking.
- Availability: the uninitialized resource may contain values that change program flow in ways the programmer never intended, causing a crash, exit or restart.
The examples go further than the stated consequences, with code execution listed for two of them, which shows how an uninitialized pointer can turn into more than a leak.
How to find it
MITRE says automated static analysis (SAST) can find some instances by modeling data flow and control flow, with high effectiveness. It works well here because the question is simple: is there a path from declaration to use with no assignment?
Other checks:
- Build with warnings for uninitialized variables switched on and do not suppress them.
- Run tests under memory checking tools that report reads of uninitialized memory.
- Review error paths. Early returns and failed calls are where initialization gets skipped.
- Look at every place a buffer leaves the process, such as sockets, files and logs, and ask how much of it was actually written.
How to fix it
From the MITRE mitigations:
- Explicitly initialize the resource before use. If an API function or standard procedure does it, follow all required steps.
- Pay close attention to complex conditionals that affect initialization, since some branches might not perform it.
- Avoid race conditions during initialization routines (see CWE-362).
- Run or compile the product with settings that generate warnings about uninitialized variables or data.
Fixed versions of the earlier examples:
int get_limit(const char *name) {
int limit = DEFAULT_LIMIT;
if (lookup(name, &limit) != 0) {
return DEFAULT_LIMIT;
}
return limit;
}
char *buf = calloc(1, 256); /* zeroed */
Also send only the bytes you wrote, not the whole buffer.
Try it yourself
The SAST demo shows what a static scan reports for a pattern like the first example.
Click through it at your own pace, or open it full screen.
Quick check
What is the confidentiality risk of an uninitialized buffer?
Old contents of the memory can be sent to an untrusted party along with the new data.
Which build setting does MITRE recommend?
Compile or run with settings that warn about uninitialized variables or data.
Why do error paths matter?
CVE-2008-0063 failed to clear memory when generating an error message, which leaked information. Error handling is a common place for initialization to be skipped.