Skip to main content
November 25, 2013

A Tale of Two Compilers

What’s wrong with the following C code?

char buf[32];
scanf("%32s", buf);

It’s a classic and easy to make off-by-one error, caused by the willy-nilly inconsistency of common C functions regarding whose responsibility the null terminator is and whether it’s included in a passed count of bytes. In this case, scanf() will read up to 32 bytes from standard input and then append a null terminator, which overflows the buffer of 32 characters and writes a null byte to whatever happens to be next on the stack. If you compile this program, run it, and give it a long input, you might see the stack corrupt and the program output 0xabad1d00. Or… you might not.

#include <stdio.h>

int main(int argc, char *argv[]) {

    int x = 0xabad1dea; // the best number.
    char buf[32];

    scanf("%32s", buf);
    printf("%s 0x%xn", buf, x);

    return 0;

When I run this with the input of the alphabet written out twice in a row (52 characters), the output stops at the second “F” (that’s character 32) and correctly displays 0xabad1dea. So, everything’s fine, right? The compiler must have fixed it for us. Thanks, compiler! The compiler is actually unintentionally stabbing us in the back. (Though every programmer suspects it might sometimes be intentional.) For whatever reason suits it, it has chosen to leave empty padding bytes on the stack between the buffer and the integer, which means that slightly overflowing the buffer does not cause any corruption of other variables at this time. What happens when at some later point in the program, you either use an unbounded string copy on the assumption it’s 32 bytes or less, or copy up to 32 bytes, assuming the null terminator is included in that bound? You start corrupting other things, which may or may not be immediately apparent for the same happenstance reasons it may or may not have been immediately apparent the first go around. Your code may crash or output corrupted data three thousand lines and seventeen copy operations away. On my Mac I have two different compilers (llvm-gcc and clang) and they output two different things for the above program when compiled for 64-bit with no extra flags: 0xabad1d00 and 0xabad1dea respectively. Neither is wrong: it’s my program that has a bug, and whether or not that bug affects the output depends on choices the compiler is entitled to make about stack layout. Both compilers are using stack guard protection, but – in the case of this protection scheme – it only works if we overflow the end of a stack frame. Since the buffer is not the highest variable on the stack, overflowing it by one byte does not overwrite the guard value at the end of the stack frame and trigger an abort trap. You can experiment with this by changing the scan format string to use a much larger number (try 64) and feeding it that much data. You can also empirically determine exactly how many bytes of padding are between the buffer and the integer but again: this is entirely implementation-dependent and it can change between every run if that suits the compiler’s mood. (Four. On my machine with this version of clang, there are four bytes of dead space, and zero with llvm-gcc.) A key point to realize is that a bug in your code that may have been entirely latent for some time will suddenly manifest when you change compilers, including updating your current one. Don’t blame the poor compiler! The bug was yours all along. As my mentor in security paranoia taught me: Constant Vigilance! Never rely on a compiler “fixing” a bug for you. Be proactive in killing them even if they don’t seem to be hurting anything. It just so happens that one of the features of Veracode’s binary analysis service is detecting off-by-one (or off-by-a-lot) errors in C and C++ such as this one – at a real-world scale.

Melissa Elliott is an application security researcher who has been writing loud opinions from a quiet corner of the Veracode office for two years and counting. She enjoys yelling about computers on Twitter and can be bribed with white chocolate mocha.

Love to learn about Application Security?

Get all the latest news, tips and articles delivered right to your inbox.