Stack buffer overflows are an all too common failure mode in embedded systems where a program accidentally, or maliciously, overwrites fixed-length buffers on the call stack resulting in loss of system integrity. This blog describes how the ARM Compiler incorporates a stack protection security feature which can prevent stack buffer overflows, thus improving overall system integrity.
Call Stack Basics
A call stack, or simply ‘stack,’ is a data structure that stores information about software subroutines which are active during software execution. When a subroutine is called, a ‘stack frame’ is created where pertinent data is stored, such as the subroutine’s return address, values to be passed from caller to callee, values to be returned to the caller, and stored values of local variables of the subroutine. If a second (nested) subroutine is called, a new stack frame is created in adjoining memory. A register called the stack pointer (SP) points to the bottom of the stack while the top of the stack is located at a fixed address. Figure 1 below shows an example stack based on the ARM Procedure Call Standard (PCS):
Problems arise when a program attempts to use more memory than the stack has available. For example, if a subroutine uses an array as a local variable, an application could overwrite the array by writing more values than the array was allocated, thus overwriting adjacent memory. Writing past the end of an array will corrupt any stack data following the array, which could include the stack frame return address. Here is an example:
void string_copy(char *p)
char small_buf; //small_buf not big enough to hold large_buf
strcpy(small_buf,p); //usage of strcpy doesn’t allow for bounds checking
The function string_copy begins copying large_buf value by value into small_buf, but small_buf is much smaller than the string being copied. Because the strcpy function copies without bounds checking, the routine will carry on until all 128 characters are written to small_buf, causing the buffer to overflow and large_buf values to begin overwriting other values on the stack, including other local variables, and potentially the stack frame return address. Best case, your program will simply crash. Worst case, the buffer overwrite is malicious and forces the return address to a sequence of code which enables unwanted control of your device.
Stack Protection – How it Works
The Stack Protection feature provided by the ARM Compiler protects against damaging or malicious buffer overruns by adding a user-defined value, called a Stack Guard, between vulnerable local variables, e.g. arrays with type char or wchar_t, and the stack frame return address. Figure 2 below shows an example of a protected call stack.
The Stack Guard has a copy stored in a user-defined memory location. The ARM Compiler automatically generates code which compares the Stack Guard value to its copy prior to loading the value of the return address into the Program Counter (PC). If the Stack Guard and its copy do not match, an error handler is called.
To further limit the potential damage caused by a stack buffer overrun, the ARM Compiler moves vulnerable types to the top of the stack, immediately preceding the Stack Guard. A copy of the Stack Guard is stored at another location and is used to check that the guard value has not been overwritten, indicating a buffer overflow.
The ARM Compiler offers command line options and predefined functions to help setup and manage the Stack Guard. These options include:
- protect_stack : inserts a guard variable onto the stack frame for each function containing a vulnerable variable. The guard variable is inserted between any buffers and the return address entry. The compiler uses heuristics to determine which functions will be protected.
- protect_stack_all : option adds this protection to all functions regardless of their vulnerability. Use this option to bypass the compiler heuristics or to get a worst case estimate when measuring performance impact of the stack protection feature.
For security reasons, the value of the Stack Guard and the location of the Stack Guard copy are defined by the user. The value of the Stack Guard is taken from a global variable defined within:
The user should provide this variable with a suitable value, such as a random value. The value can change during the life of the program. A desirable implementation might be to have the value constantly changed by another thread. If the Stack Guard fails to match its copy, the following function is called:
Within this function, the user needs to implement the stack-corruption handler.
Poor programming techniques or malicious behaviour have the ability to paralyze or even hijack your device. Particular sources of vulnerability are array buffers on the program call stack. The ARM Compiler Stack Protection feature mitigates this risk by adding an extra level of security to your system.
CoreMark and Compiler Performance