My name is Hongwei Qi and I am a Software Design Engineer in Test on the Visual C++ compiler code generation team. In this post I want to share with you the enhancements planned for the GS feature in Visual Studio 2010.
Evolution of GS
A lot of code written in C and C++ has vulnerabilities that leave their users open to buffer overrun attacks. There are two major reasons for this. One reason is that the languages provide unfettered access to the vulnerable memory; the other reason is that developers make mistakes. The simple fact is that even following the best practices and performing quality checks, by the end of the day, no developers can get 100 percent of their code right all the time. Thus, additional built-in layers of defense to help track down vulnerable areas of code are in order. The Visual C++ compiler’s GS switch, which is on by default, is one of the built-in defenses designed to mitigate the buffer overrun attacks.
The GS switch was first provided in Visual Studio .NET 2002. It detects certain kinds of stack buffer overruns and terminates the process at runtime. When code is compiled by the compiler, the GS switch injects a cookie in a function’s stack just before its return address. The cookie enables the runtime to compare the value at the beginning and at the end of a function. If the value has changed, a buffer overrun has occurred, and it is no longer safe to run the application. Since the GS switch was first made available in Visual Studio .NET 2002, it has been constantly evolving. Visual Studio .NET 2003 SP1 added SafeSEH support. In Visual Studio 2005, the improvements included parameter shadowing – making a shadow copy of a pointer parameter into a local variable. This step can help mitigate pointers that reside on the parameter stack from being exploited through a buffer overrun. In Visual Studio 2005 SP1, a new pragma, strict_gs_check, was added, which makes GS more aggressive by checking any function that has an address-taken local variable.
Here are some good links to refer to regarding GS switch:
· http://msdn.microsoft.com/en-us/library/8dbf701c.aspx
· http://msdn.microsoft.com/en-us/library/aa290051.aspx
· http://msdn.microsoft.com/en-us/library/bb507721.aspx
A great witness to the success of GS switch
The ability of the GS switch to mitigate buffer overruns was first put to the test in 2003. Soon after the release of Windows Server 2003, a new virus, the Blaster worm, invaded thousands of computers. But the Blaster worm was not able to hijack applications that ran on Windows Server 2003. The reason was because the code for Windows Server 2003 had been compiled by using the Visual C++ compiler. And as a result of having the GS switch, the code had been scrutinized and had foiled the attempted invasion.
GS Enhancement in VC++ 2010
Building on the many successes of the GS switch in the past and in alignment with Microsoft’s commitment to increase security around its products and to provide development tools that would enable its customers to do the same, the VC++ compiler team is proactively working to refine and enhance the abilities of the GS switch. There are two key areas that have been addressed. One is to apply the existing GS technologies to a wider scope of protected functions. The other is to optimize away the unneeded security cookies for the functions that are safe from buffer overruns through GS analysis improvement.
Widen the protection scope:
In the current GS heuristic, the algorithms determining whether to emit a security GS cookie, primarily protects those vulnerable functions which have local string buffer (character based array). A GS buffer is defined as an array whose element size is one or two bytes, and where the size of the whole array is at least five bytes, or any buffer allocated with _alloca. Note that byte arrays and ushort arrays are protected too, since the GS heuristic does not treat them differently from char/wchar. Functions which have local array and the array element size is bigger than two bytes, like array of ints, longs, and structs, etc., are not guarded and are open to potential buffer overruns.
While with the widened scope of GS switch feature, most kinds of buffers and data structures are going to be protected. Using the existing MSDN example, when you compile the following code with /GS, with the current GS heuristic, no GS cookie is inserted on the stack, because the buffer element data type is an unsigned integer, although the buffer is subject to being overrun. However with the widened GS switch, GS cookie will be injected to protect the return address.
unsigned int * ReverseArray(unsigned int *pdwData, size_t cData)
{
// *** This buffer is subject to being overrun!! ***
unsigned int dwReversed[20];
// Reverse the array into a temporary buffer
for (size_t j=0, i = cData; i ; –i, ++j)
// *** Possible buffer overrun!! ***
dwReversed[j] = pdwData[i];
// Copy temporary buffer back into input/output buffer
for (size_t i = 0; i < cData ; ++i)
pdwData[i] = dwReversed[i];
return pdwData;
}
GS Optimization:
As we can see using the widened GS protection allows developers to ensure that their applications benefit from the GS mitigation in a much greater variety of stack buffer overflow scenarios. However one concern you might have is whether this will affect the applications’ performance since, in order to protect the function’s return address, extra code is inserted into the prolog and epilog of a function with a GS buffer:
In the function prolog, place security cookie on stack between return address and local variables:
push ecx
mov eax, DWORD PTR ___security_cookie
xor eax, esp
mov DWORD PTR __$ArrayPad$[esp+4], eax
In the function epilog, check the security cookie is not changed:
mov ecx, DWORD PTR __$ArrayPad$[esp+4]
xor ecx, esp
call @__security_check_cookie@4
pop ecx
ret 0
Stack reordering also causes extra code size, and parameter shadowing causes extra instructions to be inserted in the prolog. Theoretically, those extra instructions and calls might affect the performance matrix on both execution time and code size. However if the compiler improves its analytical ability and can better identify the functions where no GS cookie is needed, then the performance gain you get through reducing the unneeded guarding should offset the performance lost due to widening the range of protection. This is the second area we have been addressing. For instance, the current GS switch will inject a security cookie for the function Bar() in the following example, neglecting the fact that the function Bar() is safe from buffer overrun. While with the enhanced GS, the compiler can track all the local GS buffers and the pointer parameters usage information and identify that array string[20] in Bar() is immune from a buffer overrun attack, so the compiler won’t emit a security cookie for Bar().
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFFERSIZE 40
wchar_t Buff[BUFFERSIZE] = L”Hello”;
void foo(wchar_t *p, int count){
memcpy(p, Buff, (count * sizeof(*p)));
}
void bar() {
wchar_t string[20];
foo(string, _countof(string));
wprintf(string);
}
int main(){
bar();
return 0;
}
Additionally, we are going to introduce a new declspec allowing a developer to selectively disable GS protection from a particular function. Marking a function as __declspec(safebuffers) tells the compiler that the buffers in this function are safe and it does not need security cookie protection. We are going to provide users the option to choose different level of GS protections through /GS:n: /GS:1 is equivalent to the existing /GS in VC++ 2005 and 2008 and /GS:2 is the widened scope protection. /GS is the compiler’s default setting and maps to /GS:2.
Conclusion:
With the continuing enhancement of the Visual C++ compiler and the GS switch, VC++ compiler team is one step further to help the customers improve the security and reliability of their applications.
Thank you,
Hongwei Qi
Note*: The widened scope of GS switch feature will be in VC++ 2010 beta1 release and the GS Optimization will be in post beta1.
0 comments