Stack smash protection
A stack smash attack is a type of attack based on a
buffer overflow which raises serious
computer security vulnerabilities. There have been many attempts to protect against these, with
stack smash protection, two of the most common being StackGuard and ProPolice.
How it works
\nStack smash protection modifies the organization of data on the stack to include a "canary" value which, when destroyed, shows that a buffer before it has been overflowed. This imposes a negligible performance impact (see StackGuard benchmarks) while giving the benefit of preventing an entire class of attacks.
An example
\nNormal buffer allocation for x86 architectures and other similar architectures is shown in the Buffer Overflow entry. Here, we will show the modified process as it pertains to StackGuard
Before calling a function, the stack looks like this:
(DATA)(DATA)(...)
When a function is called, the return address and control information is pushed onto the stack, as so:
(CTL_INFO)(RETADDR)(DATA)(DATA)(...)
In our example, a buffer `char a[10]` is allocated on the stack. Before we can do this, we must stick a "canary" on the stack. This canary is one of three types, which we will get to later. The stack looks like this now:
(CANARY)(CTL_INFO)(RETADDR)(DATA)(DATA)(...)
Next, the buffer is allocated, giving the following view:
(a.........)(CANARY)(ADDR)(DATA)(DATA)(...)
When the buffer is deallocated at the function's end, the canary is checked against data somewhere else in ram. If the check passes, the program continues. If not, the program crashes.
Let's consider what happens if the 10 byte buffer 'a' is overflowed with a 14 byte buffer. Here's the general view:
(0123456789)(0123RY)(ADDR)(DATA)(DATA)(...)
The first four bytes of the canary are overwritten by the last 4 bytes of the buffer 'a'. It won't pass the check, and the program will end. Note that without the canary, the program would return to whatever those last four bytes point to, altering the execution flow. If the buffer was longer than 14 bytes, an attacker could inject executable code (
shellcode) or return to existing code (
ret2libc).
This technique adds a few instructions of overhead for every dynamic buffer allocation and deallocation. The overhead generated in this technique is not significant. It does work, though, unless the canary remains unchanged. If the attacker can guess the canary, and knows that it's there, he may simply copy over it with itself. This is highly implausible in most situations.
It may be noted that the position of the canary is implimentation specific, but it is always between the buffers and the data that needs to be protected. Varied positions have varied benefits.
Canaries
\nCanaries are known values that are placed between a buffer and control data on the stack to monitor buffer overflows. When The buffer overflows, it will clobber the canary, making the overflow evident.
There are three types of canaries in use: Terminator, Random, and Random XOR. Current versions of StackGuard support all three, while ProPolice supports Terminator and Random canaries.
Terminator canaries
\nTerminator Canaries are based on the observation that most buffer overflows and stack smash attacks are based on certain string operations which end at terminators. The reaction to this observation is that the canaries are built of NULL terminators, CR, LF, and -1. The undesirable result is that the canary is known. Even with the stack smash protection, an attacker could potentially overwrite the canary and control information, then go back and write a shorter overflow to fix the canary. This is only effective in rare cases where double-overflows are possible.
Random canaries
\nRandom canaries are randomly generated, usually from an entropy gathering daemon, so an attacker doesn't know what it is. It is not usually logically possible or plausable to read the canary for exploiting; the canary is a secure value known only by those who need to know it, the SSP code in this case.
Normally, a random canary is generated at program initialization, and stored in a global variable. This variable is usually padded by unmapped pages, so that attempting to read it using any kinds of tricks that exploit bugs to read off ram cause a segmentation fault, terminating the program. It may still be possible to read the canary, if the attacker knows where it is, or can get the program to read from the stack.
Random XOR canaries
\nRandom XOR Canaries are Random Canaries that are XOR scrambled using all or part of the control data. In this way, once the canary or the control data is clobbered, the canary value is wrong.
Random XOR Canaries have the same vulnerabilities as Random Canaries, except that the 'read from stack' method of getting the canary is a bit more complicated. The attacker must get the canary, the algorithm, and the control data to generate the original canary for re-encoding into the canary he needs to use to spoof the protection.
In addition, Random XOR Canaries can protect against a certain type of attack involving overflowing a buffer in a structure into a pointer to change the pointer to point at a piece of control data. Because of the XOR encoding, the canary will be wrong if the control data or return value is changed. Because of the pointer, the control data or return value can be changed without overflowing over the canary.
Although these canaries protect the control data from being altered by clobbered pointers, they do not protect any other data or the pointers themselves. Function pointers especially are a problem here, as they can be overflowed into and will execute shellcode when called.
Things SSP cannot protect
\nStack Smash Protection is unable to protect against certain forms of attack. For example, SSP cannot protect against buffer overflows in heap; that would be left to Heap Smash Protection.
StackGuard and ProPolice cannot protect against overflows in automatically allocated structures which overflow into function pointers. ProPolice at least will rearrange the allocation order to get such structures allocated before function pointers.
There is no sane way to alter the structure of a structure; structures are expected to be the same between modules, especially with shared libraries. Any data in a structure after a buffer is impossible to protect with canaries; thus, programmers must be very careful about how they organize their variables and use their structures. Structures with buffers should be malloc()ed or new[]ed pointers.
Implementations
\nStackGuard is suggested for implementation in gcc according to the GCC 2003 Summit Proceedings and
the StackGuard homepage; however,
gcc uses neither StackGuard nor ProPolice as of 3.3.3.
Gentoo Linux uses ProPolice according to gcc -v:
gcc version 3.3.3 20040217 (Gentoo Linux 3.3.3, propolice-3.3-7)
StackGuard as of yet only exists as patches for
gcc versions up to 2.95.
ProPolice is implemented for
gcc as a patch, up to
gcc version 3.4.0. The patch is used in
Gentoo Linux and
OpenBSD, and stack smash protection can be attained by adding
-fstack-protector to the CFLAGS in make.conf.
References
\n*The GCC 2003 Summit Proceedings\n*
Immunix StackGuard Performance Benches\n*
Smashing the Stack for Fun and Profit
External links
\n*ProPolice official home\n*
Immunix StackGuard Homepage