In section Section 5.1.2.3 Program execution, the C99 standard of the C Standard [ISO/IEC 9899:19992011] states that:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
This clause gives compilers the leeway to remove code deemed unused or unneeded when building a program. While Although this functionality is usually beneficial, sometimes the compiler removes code that it thinks is not needed but that has been added with security in mind. An example of this is overwriting the memory of a buffer that is used to store sensitive data. As a result, care must always be taken when dealing with sensitive data to ensure that operations on it always execute as intended.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (GetPassword(pwd, sizeof(pwd))) {
/* checking of password, secure operations, etc */
}
memset(pwd, 0, sizeof(pwd));
}
|
...
This noncompliant code example accesses the buffer again after the call to memset(). This technique prevents some compilers from optimizing out the call to memset() but does not work for all implementations. For example, the MIPSpro compiler and versions 3 and later of GCC cleverly nullify only the first byte and leave the rest intact. Check compiler documentation to guarantee this behavior for a specific platform.
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/*checking of password, secure operations, etc */
}
memset(pwd, 0, sizeof(pwd));
*(volatile char*)pwd= *(volatile char*)pwd;
}
|
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/* checking of password, secure operations, etc */
}
ZeroMemory(pwd, sizeof(pwd));
}
|
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/* checking of password, secure operations, etc */
}
SecureZeroMemory(pwd, sizeof(pwd));
}
|
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/* checking of password, secure operations, etc */
}
#pragma optimize("", off)
memset(pwd, 0, sizeof(pwd));
#pragma optimize("", on)
}
|
...
This compliant solution uses the volatile type qualifier to inform the compiler that the memory should be overwritten and that the call to the memset_s() function should not be optimized out. Unfortunately, this compliant solution may not be as efficient as possible due to because of the nature of the volatile type qualifier preventing the compiler from optimizing the code at all. Typically, some compilers are smart enough to replace calls to memset() with equivalent assembly instructions that are much more efficient than the memset() implementation. Implementing a memset_s() function as shown in the example may prevent the compiler from using the optimal assembly instructions and can result in less efficient code. Check compiler documentation and the assembly output from the compiler.
| Code Block | ||||
|---|---|---|---|---|
| ||||
/* memset_s.c */
errno_t memset_s(void *v, rsize_t smax, int c, rsize_t n) {
if (v == NULL) return EINVAL;
if (smax > RSIZE_MAX) return EINVAL;
if (n > smax) return EINVAL;
volatile unsigned char *p = v;
while (smax-- && n--) {
*p++ = c;
}
return 0;
}
/* getPassword.c */
extern errno_t memset_s(void *v, rsize_t smax, int c, rsize_t n);
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/*checking of password, secure operations, etc */
}
if (memset_s(pwd, sizeof(pwd), 0, sizeof(pwd)) != 0) {
/* Handle error */
}
}
|
This is the preferred solution for C99 C because C1X C11 introduces a memset_s function with this signature. See the C1X C11-compliant solution for more information.
However, note that both calling functions and accessing volatile-qualified objects can still be optimized out (while maintaining strict conformance to the standard), so without a C1X C-conforming implementation, this compliant solution still might still not work in some cases.
Compliant Solution (
...
C11)
C1X includes The C Standard includes a memset_s function. According to the November 24, 2009 C1X Committee Draft [Jones 2009]. Section K.3.7.4.1, paragraph 4 [ISO/IEC 9899:2011], states:
Unlike
memset, any call to thememset_sfunction shall be evaluated strictly according to the rules of the abstract machine as described in (5.1.2.3). That is, any call to thememset_sfunction shall assume that the memory indicated bysandnmay be accessible in the future and thus must contain the values indicated byc.
| Code Block | ||||
|---|---|---|---|---|
| ||||
void getPassword(void) {
char pwd[64];
if (retrievePassword(pwd, sizeof(pwd))) {
/* checking of password, secure operations, etc */
}
memset_s(pwd, 0, sizeof(pwd));
}
|
...
CERT C++ Secure Coding Standard: MSC06-CPP. Be aware of compiler optimization when dealing with sensitive data
ISO/IEC 9899:1999 Section 2011 Section 6.7.3, "Type qualifiers"
MITRE CWE: CWE-14, "Compiler Removal removal of Code code to Clear Buffersclear buffers"
...
Sources
[Jones 2009|AA. Bibliography#Jones 09]ISO/IEC 9899:2011] Section K.3.7.4.1, "The memset_s function"
[US-CERT] "MEMSET"
[MSDN] "SecureZeroMemory"
[MSDN] "Optimize (C/C++)"
[Wheeler 2003] Section 11.4, "Specially Protect Secrets protect secrets (Passwords passwords and Keyskeys) in User Memoryuser memory"
...