Buffer Overflow Protection - An Example of Canaries

An Example of Canaries

Normal 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.

When a function is called, a stack frame is created. A stack frame is built from the end of memory to the beginning; and each stack frame is placed on the top of the stack, closest to the beginning of memory. Thus, running off the end of a piece of data in a stack frame alters data previously entered into the stack frame; and running off the end of a stack frame places data into the previous stack frame. A typical stack frame may look as below, having a Return Address (RETA) placed first, followed by other control information (CTLI).

(CTLI)(RETA)

In C, a function may contain many different per-call data structures. Each piece of data created on call is placed in the stack frame in order, and is thus ordered from the end to the beginning of memory. Below is a hypothetical function and its stack frame.

int foo { int a; /*integer*/ int *b; /*pointer to integer*/ char c; /*character array*/ char d; b = &a; /*initialize b to point to location of a*/ strcpy(c,get_c); /*get ''c'' from somewhere, write it to ''c''*/ *b = 5; /*the data at the point in memory ''b'' indicates is set to 5*/ strcpy(d,get_d); return *b; /*read from ''b'' and pass it to the caller*/ } (d..)(c.........)(b...)(a...)(CTLI)(RETA)

In this hypothetical situation, if more than ten bytes are written to the array c, or more than 3 to the character array d, the excess will overflow into integer pointer b, then into integer a, then into the control information, and finally the return address. By overwriting b, the pointer is made to reference any position in memory, causing a read from an arbitrary address. By overwriting RETA, the function can be made to execute other code (when it attempts to return), either existing functions (ret2libc) or code written into the stack during the overflow.

In a nutshell, poor handling of c and d, such as the unbounded strcpy calls above, may allow an attacker to control a program by influencing the values assigned to c and d directly. The goal of buffer overflow protection is to detect this issue in the least intrusive way possible. This is done by removing what can be out of harms way and placing a sort of tripwire, or canary, after the buffer.

Buffer overflow protection is implemented as a change to the compiler. As such, it is possible for the protection to alter the structure of the data on the stack frame. This is exactly the case in systems such as ProPolice. The above function's automatic variables are rearranged more safely: arrays c and d are allocated first in the stack frame, which places integer a and integer pointer b before them in memory. So the stack frame becomes

(b...)(a...)(d..)(c.........)(CTLI)(RETA)

As it is impossible to move CTLI or RETA without breaking the produced code, another tactic is employed. An extra piece of information, called a "canary" (CNRY), is placed after the buffers in the stack frame. When the buffers overflow, the canary value is changed. Thus, to effectively attack the program, an attacker must leave definite indication of his attack. The stack frame is

(b...)(a...)(d..)(c.........)(CNRY)(CTLI)(RETA)

At the end of every function there is an instruction which continues execution from the memory address indicated by RETA. Before this instruction is executed, a check of CNRY ensures it has not been altered. If the value of CNRY fails the test, program execution is ended immediately. In essence, both serious attacks and harmless programming bugs result in a program abort.

The canary technique adds a few instructions of overhead for every function call with an automatic array, immediately before all dynamic buffer allocation and after dynamic buffer deallocation. The overhead generated in this technique is not significant. It does work, though, unless the canary remains unchanged. If the attacker knows that it's there, he may simply copy over it with itself. This is usually difficult to arrange intentionally, and highly improbable in unintentional situations.

The position of the canary is implementation specific, but it is always between the buffers and the protected data. Varied positions and lengths have varied benefits.

Read more about this topic:  Buffer Overflow Protection