Learn how to prevent stack-based buffer overflows.
In C and C++, memory for local variables is allocated in a chunk of memory called the stack. Information pertaining to the control flow of a program is also maintained on the stack. If an array is allocated on the stack and that array is overrun (that is, more values are pushed into the array than the available space provides), an attacker can overwrite the control flow information that is also stored on the stack. This type of attack is often referred to as a stack-smashing attack.
Stack-smashing attacks are a serious problem, since an otherwise innocuous service (such as a web server or FTP server) can be made to execute arbitrary commands. Several technologies have been developed that attempt to protect programs against these attacks. Some are implemented in the compiler, such as IBM’s ProPolice (http://www.trl.ibm.com/projects/security/ssp/) and the Stackguard (http://www.immunix.org/stackguard.html) versions of GCC. Others are dynamic runtime solutions, such as LibSafe (http://www.research.avayalabs.com/project/libsafe/). While recompiling the source gets to the heart of the buffer overflow attack, runtime solutions can protect programs when the source isn’t available or recompiling simply isn’t feasible.
All of the compiler-based solutions work in much the same way, although there are some differences in the implementations. They work by placing a “canary” (which is typically some random value) on the stack between the control flow information and the local variables. The code that is normally generated by the compiler to return from the function is modified to check the value of the canary on the stack; if it is not what it is supposed to be, the program is terminated immediately.
The idea behind using a canary is that an attacker attempting to mount a stack-smashing attack will have to overwrite the canary to overwrite the control flow information. By choosing a random value for the canary, the attacker cannot know what it is and thus cannot include it in the data used to “smash” the stack.
When a program is distributed in source form, the developer of the program cannot enforce the use of StackGuard or ProPolice, because they are both nonstandard extensions to the GCC compiler. It is the responsibility of the person compiling the program to make use of one of these technologies.
For Linux systems, Avaya Labs’s LibSafe technology is not implemented as a compiler extension, but instead takes advantage of a feature of the dynamic loader that causes a dynamic library to be preloaded with every executable. Using LibSafe does not require the source code for the programs it protects, and it can be deployed on a system-wide basis.
LibSafe replaces the implementation of several standard functions
that are known to be vulnerable to buffer overflows, such as
scanf(). The replacement implementations attempt
to compute the maximum possible size of a statically allocated buffer
used as a destination buffer for writing, using a GCC built-in
function that returns the address of the frame pointer. That address
is normally the first piece of information on the stack following
local variables. If an attempt is made to write more than the
estimated size of the buffer, the program is terminated.
Unfortunately, there are several problems with the approach taken by
LibSafe. First, it cannot accurately compute the size of a buffer;
the best it can do is limit the size of the buffer to the difference
between the start of the buffer and the frame pointer. Second,
LibSafe’s protections will not work with programs
that were compiled using the
flag to GCC, an optimization that causes
the compiler not to put a frame pointer on the stack. Although
relatively useless, this is a popular optimization for programmers to
employ. Finally, LibSafe will not work on SUID binaries without
static linking or a similar trick.
In addition to providing protection against conventional stack-smashing attacks, the newest versions of LibSafe also provide some protection against format-string attacks. The format-string protection also requires access to the frame pointer because it attempts to filter out arguments that are not pointers into either the heap or the local variables on the stack.
In addition to user-space solutions, you can also opt to patch your kernel to use nonexecutable stacks and detect buffer overflow attacks. We’ll do just that in [Hack #13] .