Hardening Stack-based Buffer Overrun Detection in VC++ 2005 SP1

As y’all know, the Visual C++ /GS compiler flag adds prolog and epilog code to certain functions to help detect some classes of stack based buffer overruns at runtime. In VC++ 2005, the code looks like this:

Function prolog

sub esp, 8

mov eax, DWORD PTR ___security_cookie

xor eax, esp

mov DWORD PTR __$ArrayPad$[esp+8], eax

mov eax, DWORD PTR _input$[esp+4]

Function epilog

mov ecx, DWORD PTR __$ArrayPad$[esp+12]

add esp, 4

xor ecx, esp

call @__security_check_cookie@4

add esp, 8

__security_cookie is a pseudo-random number created when the image loads.

In the overall scheme of things, we see little if any performance degradation because of the /GS. But there are a set of heuristics you should be aware when compiling with /GS. In short, the compiler does not apply the stack cookie code to all functions; it applies a set of heuristics to determine which functions should get the stack cookie treatment. Read the /GS documentation <https://msdn2.microsoft.com/en-US/library/8dbf701c.aspx> for an explanation of the compiler heuristics.

In Visual C++ 2005 SP1, we added a new pragma that makes the -GS flag much more aggressive. The syntax is:

#pragma strict_gs_check([push,] on )
#pragma strict_gs_check([push,] off )
#pragma strict_gs_check(pop)

Personally, I enable this at the top of any source code file that includes functions highly susceptible to attack or code that handles untrusted data.

Here’s a quick code sample. The code has a buffer overrun copying an array to a local array, but when it is compiled with /GS, no cookie is inserted in the stack because the array data type is an unsigned integer. Adding the strict_gs_check pragma forces the stack cookie into the function stack.

#pragma strict_gs_check(on)

unsigned int * ReverseArray(__in_ecount(cData) unsigned int *pdwData, size_t cData) {
unsigned int dwReversed[20]; // *** This buffer is subject to being overrun!! ***
// Reverse the array into a temporary buffer

for (size_t j=0, i = cData; i ; --i, ++j)
dwReversed[j] = pdwData[i]; // *** Possible buffer overrun!! ***

// Copy temporary buffer back into input/output buffer
for (size_t i = 0; i < cData ; ++i)
pdwData[i] = dwReversed[i];

return pdwData;
}

Also, functions with stack-based buffers that are less than 4-bytes long will also get a cookie.

As a result of applying this pragma to some sample code, there was an increase from 39 to 56 functions protected by the GS cookie out of a total of 761 functions. For high-attack surface components, which often listen on the network, the extra performance overhead due to such a small number of extra GS checks is negligible, but the extra defensive layer is valuable.