13.2 Erasing Data from Memory Securely13.2.1 ProblemYou want to minimize the exposure of data such as passwords and cryptographic keys to local attacks. 13.2.2 SolutionYou can only guarantee that memory is erased if you declare it to be volatile at the point where you write over it. In addition, you must not use an operation such as realloc( ) that may silently move sensitive data. In any event, you might also need to worry about data being swapped to disk; see Recipe 13.3. 13.2.3 DiscussionSecurely erasing data from memory is a lot easier in C and C++ than it is in languages where all memory is managed behind the programmer's back. There are still some nonobvious pitfalls, however.
One pitfall, particularly in C++, is that some API functions may silently move data behind the programmer's back, leaving behind a copy of the data in a different part of memory. The most prominent example in the C realm is
realloc( )
, which will sometimes move a piece of memory, updating the programmer's pointer. Yet the old memory location will
Another pitfall is that functions like
Compiler writers have worked hard to implement optimizations into their compilers to help make code run faster (or compile to smaller machine code). Some of these optimizations can realize significant performance gains, but sometimes they also come at a cost. One such optimization is dead-code
Unfortunately, this optimization can cause problems when writing secure code. Most commonly, code that "erases" a piece of memory that contains sensitive information such as a password or passphrase in plaintext is often eliminated by this optimization. As a result, the sensitive information is left in memory, providing an attacker a
Functions like
memset( )
do useful work, so why would dead-code elimination
int get_and_verify_password(char *real_password) {
int result;
char *user_password[64];
/* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING
*
* This is an example of unsafe code. In particular, note the use of memset( ),
* which is exactly what we are discussing as being a problem in this recipe.
*/
get_password_from_user_somehow(user_password, sizeof(user_password));
result = !strcmp(user_password, real_password);
memset(user_password, 0, strlen(user_password));
return result;
}
In this example, the variable
user_password
exists solely within the function
get_and_verify_password( )
. After the
memset( )
, it's never used again, and because
memset( )
only
Several solutions to this particular problem exist, but the code that we've provided here is the most correct when used with a compiler that conforms to at least the ANSI/ISO 9899-1990 standard, which includes any modern C compiler. The key is the use of the
volatile
keyword, which
It's proper to declare any variable containing sensitive information as
volatile
. Unfortunately, many programmers are unaware of what this keyword means, so it is frequently omitted. In addition, simply declaring a variable as
volatile
may not be enough. Whether or not it is enough often depends on how
If simply declaring a variable as
volatile
may not be enough, what more must be done? The answer is to replace calls to functions like
Here is code that implements three different
#include <stddef.h>
volatile void *spc_memset(volatile void *dst, int c, size_t len) {
volatile char *buf;
for (buf = (volatile char *)dst; len; buf[--len] = c);
return dst;
}
volatile void *spc_memcpy(volatile void *dst, volatile void *src, size_t len) {
volatile char *cdst, *csrc;
cdst = (volatile char *)dst;
csrc = (volatile char *)src;
while (len--) cdst[len] = csrc[len];
return dst;
}
volatile void *spc_memmove(volatile void *dst, volatile void *src, size_t len) {
size_t i;
volatile char *cdst, *csrc;
cdst = (volatile char *)dst;
csrc = (volatile char *)src;
if (csrc > cdst && csrc < cdst + len)
for (i = 0; i < len; i++) cdst[i] = csrc[i];
else
while (len--) cdst[len] = csrc[len];
return dst;
}
If you're writing code for Windows using the latest Platform SDK, you can use SecureZeroMemory( ) instead of spc_memset( ) to zero memory. SecureZeroMemory( ) is actually implemented as a macro to RtlSecureMemory( ) , which is implemented as an inline function in the same way that spc_memset( ) is implemented, except that it only allows a buffer to be filled with zero bytes instead of a value of the caller's choosing as spc_memset( ) does. |