It was never about Mozart. I just like little touches. Sometimes from a book, sometimes from a recital.
Executive Summary
This research shows how modern compilers (GCC/Clang) can remove security mechanisms during aggressive optimization phases "not only by assembly analysis, but practically at runtime".
1. Introduction
1.1 Problem Definition
In security-critical applications, sensitive data (passwords, cryptographic keys, API tokens) must be erased from memory after use. Developers often use the memset() function in the C standard library for this purpose:
void process_password(const char *password) {
char local_copy[64];
strcpy(local_copy, password);
// Process
authenticate(local_copy);
// Security: Remove Memory
memset(local_copy, 0, sizeof(local_copy));
}
However, modern compilers apply the following logic when optimizing this code:
"local_copy variable is never used after memset() → This write operation is unnecessary → Remove!"
This optimization is known as "Dead Store Elimination (DSE)" and is fully valid according to the C/C++ standard. However, it creates a security-critical vulnerability.
1.2 Scope of Research
In this research:
- Demonstrated how the DSE vulnerability can be exploited practically, not theoretically.
- The different behaviors of
memset()andexplicit_bzero()functions at same optimization level (-O3) are compared. - Stack Frame Reuse technique was used to realize sensitive data leakage at runtime.
- Real-world CVEs are analyzed and solutions are proposed.
2. Background
2.1 What is Dead Store Elimination?
Dead Store Elimination (DSE) is one of the compiler optimization techniques. If the value written to a variable is never read afterwards, this write operation is considered "dead store" and is removed.
Example
int calculate() {
int result = expensive_computation(); // Expensive computation
result = 42; // Previous value not used
return result;
}
The compiler removes the call to expensive_computation() because its result is not used.
2.2 DSE in Security Context
DSE in safety-critical codes is problematic:
void handle_secret(const char *secret) {
char buffer[64];
strcpy(buffer, secret);
use_secret(buffer);
memset(buffer, 0, 64); // ← This line remove
}
Compiler's point of view:
buffervariable not read aftermemset()memset()only changes memory (no side effect)- Result:
memset()call is removed
2.3 Standard C and Undefined Behavior
The C standard (ISO/IEC 9899) grants the compiler permission to optimize as long as "observable behavior" doesn't change. Removing the memset() call is a valid optimization according to the standard because:
- Function return value doesn't change
- Global state doesn't change
- No I/O operation
Therefore, GCC Bug #8537 (2002) was marked as "WONTFIX".
3. Methodology
3.1 Test Environment
Platform: Linux x86_64 (Ubuntu 24.04 LTS)
Compiler: GCC 13.3.0
Optimization: -O3 (for both versions)
Additional Flags: -fno-stack-protector (stack canary disabled)
3.2 Stack Frame Reuse Technique
Traditional methods (assembly analysis, binary string search) demonstrate the vulnerability theoretically. This research proves that the vulnerability can be practically exploited at runtime.
Technique:
- Victim function writes sensitive data to stack
- Memory clearing function is called (
memset()orexplicit_bzero()) - Function returns (stack frame "pops")
- Hacker function uses the same stack area
- Uninitialized variable reads old data from stack
Critical Point: If the clearing function was removed by the compiler, old data still exists on the stack and can be read.
3.3 Code Design
3.3.1 Vulnerable Version (memset)
#include <stdio.h>
#include <string.h>
__attribute__((noinline, optimize("O3")))
void victim_function() {
char padding[8];
char secret[32];
volatile char *ptr = secret;
memset(padding, 0, 8);
strcpy((char*)ptr, "SECRET_PASSWORD_LEAKED");
printf("[VICTIM] Secret written: %s\n", secret);
memset(secret, 0, 32); // ← Compiler will remove this!
}
__attribute__((noinline, optimize("O3")))
void hacker_function() {
char padding[8];
char stolen[32]; // ← Not initialized!
memset(padding, 0, 8);
printf("[HACKER] Reading stack: ");
for(int i=0; i<32; i++) {
char c = stolen[i];
if(c >= 32 && c < 127) putchar(c);
else if(c == 0) break;
}
printf("\n");
}
int main() {
victim_function();
hacker_function();
return 0;
}
3.3.2 Secure Version (explicit_bzero)
// ... (same code, only one line difference)
__attribute__((noinline, optimize("O3")))
void victim_function() {
// ...
explicit_bzero(secret, 32); // ← Compiler can NEVER remove this!
}
3.3.3 Design Decisions
1. __attribute__((noinline, optimize("O3")))
- Function is not inlined → Stack frame preserved
- O3 optimization applied at function level
2. volatile char *ptr
- Prevents
strcpy()operation from being removed - But doesn't protect
memset()(this is intentional!)
3. char padding[8]
- Ensures stack alignment
- Victim and hacker functions work at same offset
4. Uninitialized variable
char stolen[32];→ No value assigned- Reads garbage data from stack
5. -fno-stack-protector
- Stack canary disabled
- Ensures clean stack frame overlap
4. Experimental Results
4.1 Compilation
gcc -O3 -fno-stack-protector -o vulnerable vulnerable.c
gcc -O3 -fno-stack-protector -o secure secure.c
4.2 Runtime Output
4.3 Assembly Analysis
Vulnerable (memset):
$ objdump -d vulnerable | grep "memset@plt"
# Output: EMPTY
→ memset() calls: 0 (removed!)
Secure (explicit_bzero):
$ objdump -d secure | grep "bzero"
# Output: __explicit_bzero_chk
→ explicit_bzero() calls: 1 (preserved!)
4.4 Comparison Table
| Metric | vulnerable.c | secure.c |
|---|---|---|
| Optimization Level | -O3 |
-O3 |
| Clearing Function | memset() |
explicit_bzero() |
| Assembly Call Count | 0 | 1 |
| Runtime Leak | ✅ YES | ❌ NO |
5. Findings
5.1 Compiler Behavior
Vulnerable: memset() → Dead Store Elimination → Removed (0 calls)
Secure: explicit_bzero() → POSIX guarantee → Preserved (1 call)
5.2 Real-World CVEs
| CVE | Project | Impact | CVSS | Fix |
|---|---|---|---|---|
| CVE-2016-0777 | OpenSSH | SSH key leak | 7.5 | explicit_bzero() |
| CVE-2023-5217 | libvpx | Memory disclosure | 8.8 | bzero() |
| CVE-2024-38616 | Linux Kernel | WiFi password leak | 5.5 | memzero_explicit() |
| GCC Bug #8537 | GCC | WONTFIX (2002) | - | Developer responsibility |
6. Conclusion
Key Findings:
memset()andexplicit_bzero()behave differently at the sameO3levelmemset()is removed → Runtime leakexplicit_bzero()is preserved → Secure- Active CVEs exist even in 2024
Recommendations:
- Don't use
memset()in security-critical code - Use
explicit_bzero()/memset_s()/SecureZeroMemory() - Verify assembly output
References
- POSIX.1-2024 -
explicit_bzero()Specification - CWE-14: Compiler Removal of Code to Clear Buffers
- CERT C - MSC06-C: Beware of compiler optimizations
- CVE-2016-0777, CVE-2023-5217, CVE-2024-38616
- GCC Bug #8537 (2002, WONTFIX)