October 27, 2020
Hot Topics:

Visual C++: Protecting Against Buffer Overruns with the /GS Switch

  • By Nick Wienholt
  • Send Email »
  • More Articles »

Buffer overruns represent one of the most common security vulnerabilities that exist in software today. By allowing a hacker to modify the execution flow of a process by providing malicious input, buffer overruns can allow an entire process, machine, or domain to be compromised. If the identity that the process is running under is a highly trusted account, such as an administrator or the Local System account, the damage that the hackers can cause is severe and potentially widespread. Some of the more famous virus outbreaks in recent times, such as the Code Red and Blaster worms, were the result of buffer overruns in C/C++ code.

Buffer overruns are a simple programming mistake—they involve copying the contents of a region of memory into another region of memory that is too small to accommodate the source block. The code below demonstrates a simple example:

char* source = "A reasonably long string";
char dest[10];
::strcpy(dest, source);

In this case, the source string is 25 characters long (including the null terminator) and will be too large for the destination block of memory, which is declared on the stack. When this code executes, the stack will become corrupted, and the program typically will crash with an access violation. A security vulnerability exists if the source block of memory is provided by an external party because this allows a block of memory that modifies the stack in a specific way to be passed into the function.

When a function is called in C/C++, the return address of the calling function is placed on the stack so that execution can return to this point once the callee has completed. By calling a function that contains a potential buffer overrun, the return address can be changed and execution will jump to the location nominated by the data in the buffer. By changing the return address of the function, an attacker can get code at an arbitrary location in the process to execute. This is commonly exploited in two main ways:

  1. If the application with the vulnerability is known and widely available, the attacker can look for the address of a function that will be located at a fixed address in all process instances and modify the stack so this function is called.
  2. The instructions to execute can be passed into the process address space as part of the buffer, and an attacker can exploit this to carry out an attack.

Defending Against Buffer Overruns

The simplest defense against a buffer overrun is limiting the size of the data copied so that it is not greater than the size of the destination buffer. While applying this defense seems trivial—and, in fact, it is in contrived situations like the earlier example—experience has shown that totally eliminating the potential for buffer overruns in large C/C++ code bases is an extremely difficult undertaking. Using managed technologies such as .NET and Java can significantly reduce the potential for buffer overruns, but moving large code bases to these technologies is often impossible or inappropriate.

The reason that stack-based buffer overruns are so easily exploitable is that the return address for a function is stored on the stack by instructions that the compiler generates. Recognizing that the compiler plays a small part in causing the problem, the Visual C++ team took the approach with the release of Visual C++.NET (7.0) that the compiler could play a part in alleviating the problem. They inserted a generated cookie with a known value in the stack below the data that held the return address of a function. By using this technique, a buffer overrun that changes the value of the function's return address will also overwrite the cookie, which can be detected when the function returns. When a modified cookie is detected, a security exception is raised, and if the exception is not handled, the process that is running the code will exit. The code below shows a skeleton program with a security exception handler present:

void _cdecl sec_handler( int code, void *)
   if ( code == _SECERR_BUFFER_OVERRUN )
      printf("You had a buffer overrun\n");

int main()
   _set_security_error_handler( sec_handler );
   //main application code here

Visual C++.NET 2003 (7.1) enhances the protection against buffer overruns by moving vulnerable data structures, such as the address of exception handlers, to a position in the call stack below the area where buffers are located. In the 7.0 release of the compiler, bypassing the protection offered by the security cookie could be accomplished by corrupting sensitive data between the buffers and the cookie. However, by moving this data to a region below the buffers in the new compiler version, modifying this data with buffer overruns is no longer possible.

Figure 1 shows the conceptual layouts of a stack in versions 6, 7.0, and 7.1 of the C++ compiler. The stacks are shown growing down from higher to lower address spaces, which is the way execution stacks grow when executing. Downward growth of the stack is the reason that buffer overruns work so well, because an overrun will write to memory addresses higher than that owned by the buffer, which is the location of the vulnerable data structures.

Logical Stack Layout
Click here for a larger image.

Figure 1: Logical Stack Layout

In addition to moving the exception handler information to a position in the stack below the data buffers, the linker in Visual C++.NET 2003 also emits the address of all structured exception handlers into the header of the executable file. When an exception occurs, the operating system can check that the address nominated in the exception information stored on the stack corresponds to an exception handler recorded in the executable's header information. If this is not the case, the exception handler will not execute. Windows Server 2003 shipped with the ability to check the structured exception information, and this technology has recently been back-ported to Windows XP in Service Pack 2.

Page 1 of 2

This article was originally published on October 6, 2004

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date