Figure 2: Canary Word Next to Return Address
To be effective, detecting that the return address has been altered must happen before a function returns. StackGuard does this by placing a ``canary'' word next to the return address on the stack, as shown in Figure 2. When the function returns, it first checks to see that the canary word is intact before jumping to the address pointed to by the return address word.
This approach assumes that the the return address is unaltered IFF
the canary word is unaltered. While this assumption is not completely true
in general (stray pointers can alter any word), it is true of buffer
overflow attacks. The buffer overflow attack method exploits the fact that
the return address word is located very close to a byte array with weak
bounds checking, so the only tool the attacker has is a linear,
sequential write of bytes to memory, usually in ascending order. Under
these restricted circumstances, it is very difficult to over-write the
return address word without disturbing the canary word.
move canary-index-constant into register push canary-vector[register]Figure 3: Function Prologue Code: Laying Down a Canary
move canary-index-constant into register move canary-vector[register] into register exclusive-or register with top-of-stack jump-if-not-zero to constant address .canary-death-handler add 4 to stack-pointer < normal return instructions here> .canary-death-handler: ...Figure 4: Function Epilogue Code: Checking a Canary
The StackGuard implementation is a very simple patch to gcc
188.8.131.52. The gcc function_prologue and function_epilogue
functions have been altered to emit code to place and check canary words.
The changes are architecture-specific (in our case, i386), but
since the total changes are under 100 lines of gcc, portability
is not a major concern. All the changes in the gcc calling conventions
are undertaken by the callee, so code compiled with the StackGuard-enhanced
gcc is completely inter-operable with generic gcc .o
files and libraries. The additional instructions added to the function
prologue are shown in pseudo-assembly form in Figure 3,
and the additional instructions added to the instruction epilogue are shown
in Figure 4. Section 4
describes testing and performance of this patch.