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.

(part 2)

Posted at 2014-09-04 19:30 | Permanent link | Comments
blog comments powered by Disqus

Recent posts

Monthly Archives

Yearly Archives


RSS