Optimize for security, not just for speed

Written by Jiří Keresteš on May 9, 2019.

In today’s article, we’re going to explain why you should think twice before enabling compiler optimizations.

If you are a developer, you are probably used to pass something like -O2 to your compiler of choice. Optimized code is supposed to run faster, why wouldn’t you want that? Right? Well, your code will probably be faster, but some optimizations can actually hurt security.

Let’s see an example:

uint8_t buffer[512];

void clear(uint8_t *target, uint32_t size){
    memset(target, 0, size);
}

int main() {
    // sensitive data processing
    memset(buffer, 'A', sizeof(buffer));
    for(int i=0; i < sizeof(buffer); i++){
        putchar(buffer[i]);
    }
    // clear buffer
    clear(buffer, sizeof(buffer));
    return 0;
}

Our sample program does some data processing and then cleans up. Or does it? Let’s look at the assembly generated by gcc compiler with no optimizations (thanks to Compiler Explorer).

As you can see, main function does all the things we expect from it. That’s good, in practice it means no leftover secrets in memory. What happens with -O2?

Output assembly is a little less clear, but we can see that call to function clear() got optimized out. From performance viewpoint, this is a perfectly valid optimization called dead store elimination or DSE. We are modifying buffer content without using it afterwards. Leaving it out saves some CPU cycles, which improves speed, but cripples security.

This doesn’t mean you should compile all your code without optimizations from now on. There are multiple techniques that prevent compiler from optimizing away your memory scrubbing. You can use platform specific functions, such as explicit_bzero, sodium_memzero or SecureZeroMemory. If you don’t have these, memory barriers or volatile buffers are usually way to go.

Of course, this is not the only optimization pitfall you can encounter. If you want to know more about this topic, I personally recommend great 35C3 talk by Ilja van Sprundel.

To be extra sure, you can always check what your binary does with excellent radare2 framework, but that’s for another post.

That would be all for today, see you next Thursday!