How to zero a buffer
In cryptographic applications, it is often useful to wipe data from memory once it is no longer needed. In a perfect world, this is unnecessary since nobody would gain unauthorized access to that data; but if someone is able to exploit an unrelated problem — a vulnerability which yields remote code execution, or a feature which allows uninitialized memory to be read remotely, for example — then ensuring that sensitive data (e.g., cryptographic keys) is no longer accessible will reduce the impact of the attack. In short, zeroing buffers which contained sensitive information is an exploit mitigation technique.Alas, this is easier said than done. Consider the most obvious approach:
void
dosomethingsensitive(void)
{
uint8_t key[32];
...
/* Zero sensitive information. */
memset(key, 0, sizeof(key));
}
This looks like it should zero the buffer containing the key before
returning; but a "sufficiently intelligent" compiler — in this
case, most of them — is allowed to recognize that key
is not accessible via conforming C code after the function returns, and
silently optimize away the call to memset
. While this completely
subverts our intention, it is perfectly legal: The observable
behaviour of the program is unchanged by the optimization.
Now, we don't want to truly change the observable behaviour of our software
— but fortunately the C standard has a more liberal concept of
"observable" than most people. In particular, the C standard states that
the observable behaviour includes accesses to volatile objects.
What is a volatile object, you ask? It is an object defined with a volatile
type — originally intended for memory-mapped device registers, where the
mere act of reading or writing the "memory" location can have side effects.
These days, the volatile
keyword essentially means "you can't
assume that this acts like normal memory".
This brings us to a common attempt at zeroing buffers:
void
dosomethingsensitive(void)
{
uint8_t key[32];
...
/* Zero sensitive information. */
memset((volatile void *)key, 0, sizeof(key));
}
On most compilers this is no better: While there is a cast to a volatile
type, the pointer is immediately cast back to void *
since
that is the type of the first parameter to memset. This may produce a
warning message, but it won't prevent the optimization: The double cast
will be collapsed and the compiler will recognize that it is not handling
a volatile object.
A somewhat more nuanced attempt is the following:
static void
secure_memzero(void * p, size_t len)
{
volatile uint8_t * _p = p;
while (len--) *_p++ = 0;
}
void
dosomethingsensitive(void)
{
uint8_t key[32];
...
/* Zero sensitive information. */
secure_memzero(key, sizeof(key));
}
This does trick a few more compilers, but it isn't guaranteed to work
either: The C standard states that accesses to volatile objects
are part of the unalterable observable behaviour — but it says
nothing about accesses via lvalue expressions with volatile types.
Consequently a sufficiently intelligent compiler can still optimize the
buffer-zeroing away in this case — it just has to prove that the
object being accessed was not originally defined as being volatile.
Some people will try this with secure_memzero
in a separate C
file. This will trick yet more compilers, but no guarantees — with
link-time optimization the compiler may still discover your treachery.
Is it possible to zero a buffer and guarantee that the compiler won't optimize it away? Yes, and here's one way to do it:
static void * (* const volatile memset_ptr)(void *, int, size_t) = memset;
static void
secure_memzero(void * p, size_t len)
{
(memset_ptr)(p, 0, len);
}
void
dosomethingsensitive(void)
{
uint8_t key[32];
...
/* Zero sensitive information. */
secure_memzero(key, sizeof(key));
}
The trick here is the volatile function pointer memset_ptr
.
While we know that it points to memset
and will never
change, the compiler doesn't know that — and most importantly, even
if it figures out that we will never change the value of the function
pointer, the compiler is forbidden from assuming that the function pointer
won't change on its own (since that's what volatile objects do). If the
function pointer might change, it might point at a function which has side
effects; and so the compiler is forced to emit the function call which
causes the key
buffer to be zeroed.
UPDATE 2014-09-04: The above code is not guaranteed to work after all.
Now, I'm not the first person to look at this problem, of course, and
if you're willing to limit yourself to narrow platforms, you don't need
to write secure_memzero
yourself: On Windows, you can use
the SecureZeroMemory
function, and on C11 (are there any
fully C11-compliant platforms yet?) you can use the memset_s
function. Both of these are guaranteed (or at least specified) to write
the provided buffer and to not be optimized away.
There's just one catch: We've been solving the wrong problem.