Some bugs are invisible in the source. CWE-14 is one of them. The code says to clear a buffer holding a secret. The compiler decides the clearing is pointless and removes it. The binary ships with the secret still sitting in memory.
MITRE defines it this way: sensitive memory is cleared according to the source code, but compiler optimizations leave the memory untouched when it is not read again afterward. The technique has a name, dead store removal.
This affects C and C++ programs, and compiled code generally. If you write code that handles passwords, keys or tokens in native languages, it applies to you.
How it happens
MITRE lays out the sequence in three steps:
- Secret data is stored in memory.
- The secret is scrubbed by overwriting its contents.
- The code is compiled by an optimizing compiler, which sees the overwrite as a dead store, because nothing reads that memory afterward, and removes it.
From the compiler's point of view this is correct. The language rules say the program's visible behavior is unchanged if a write that nobody reads is skipped. The compiler cannot know that an attacker might read a memory dump later.
Here is the pattern in C:
void check_login(const char *input) {
char password[64];
get_password(password, sizeof password);
verify(input, password);
/* Intended to wipe the secret. An optimizer may delete this call. */
memset(password, 0, sizeof password);
}
The buffer goes out of scope right after the memset. Nothing reads it again. That is exactly the situation in which an optimizer is allowed to drop the write.
What an attacker gains
MITRE lists the impact as confidentiality and access control: reading memory and bypassing a protection mechanism. Data that was supposed to be gone can be read. If it holds password material, the attacker reads the password and uses it to get past protections.
How would they read it? Through a crash dump, a swap file, a core file, a memory disclosure bug elsewhere in the program, or an attacker with access to the host. This weakness does not create the access. It makes sure there is something worth finding once someone has it.
How to find it
This is where CWE-14 is unusual, and MITRE is direct about it. The weakness is impossible to detect with black box methods. An analyst could examine memory and see that the secret was not scrubbed, but analyzing the executable would not reveal the cause, because the compiler already removed the relevant code. Only source code analysis, or a deliberate look at the compiler output, can show it. The record calls it detectable only through white box methods, and says careful analysis is needed to judge whether the code is likely to be removed.
Practical steps:
- Search the source for memset or similar calls that clear a buffer just before it goes out of scope or is freed.
- Compile with your release flags and inspect the generated assembly for those functions. If the wipe is missing, you have the bug.
- Run a test that dumps process memory after the wipe and looks for the secret. Run it against the optimized build, not the debug build, because debug builds usually keep the write.
That last point trips teams up. The wipe works in debug and disappears in release.
How to fix it
The record does not list mitigations, but the remedy follows from the cause. Use a clearing function that the compiler is not allowed to remove.
- C11 provides memset_s as an optional feature.
- Many platforms provide explicit_bzero.
- Windows provides SecureZeroMemory.
- Libraries for cryptography commonly include their own secure wipe routine.
#include <string.h>
void check_login(const char *input) {
char password[64];
get_password(password, sizeof password);
verify(input, password);
explicit_bzero(password, sizeof password);
}
Availability varies by platform, so check what your toolchain supports and pick one standard helper for the whole codebase.
Other steps help as well:
- Keep secrets in memory for as short a time as possible.
- Avoid copying secrets into extra buffers, since each copy needs its own wipe.
- Where the platform allows it, lock secret pages so they do not reach swap.
- Add a code review rule: any plain memset on a secret buffer gets flagged.
- Turn the memory check above into an automated test on the release build.
Quick check
Why does the wipe survive in debug builds but vanish in release?
Debug builds usually switch optimization off, so the write stays. Release builds optimize, and the compiler removes the write as a dead store.
Can a penetration test find this?
Not reliably. MITRE says black box methods cannot detect it, since the removed code leaves no trace in the executable's behavior. Source review or compiler output inspection is needed.
Does making the buffer volatile solve it?
It can discourage removal in some cases, but it is not a dependable guarantee across compilers. A purpose built secure wipe function is the safer choice.