Virtual Developer Workshop: Containerized Development with Docker
C run-time libraries (CRT) functions like memcpy date back to a time when it was cool to wear your hair long and drop vowels from function names. These functions were conceived and implemented in a world of low-connectivity where attackers weren't scanning every port of your clients' and end-users' PCs over a broadband connection, attempting to find software vulnerabilities such as buffer overruns. In those good old days of the C runtime, the worst-case scenario for a buffer overrun vulnerability was an operating system crash.
Times have changed. The cleanup cost of major attacks such as Slammer and Code Red that exploit buffer overrun vulnerabilities is similar to that of nuclear reactor meltdowns (the cost of Code Red clean-up and lost productivity was $2.6 billion; approximately $973 million has been spent in the Three Mile Island cleanup). Thankfully, the use of CRT functions that make buffer overruns more likely is no longer required, and safer alternatives are available.
Non-secure CRT Functions in Legacy Code
C++ developers beginning a new project today would probably have little use for the CRT library. The Standard C++ Library, .NET Framework Library, and many third-party libraries offer much richer functionality and greater ease of use compared to the CRT. However, most developers typically have a huge code base of existing applications and libraries that they need to maintain and extend, and dealing with legacy code that makes heavy use of the CRT is a fact of life. Rather than just accept the fact that the CRT makes writing code with buffer overrun vulnerabilities much more likely, Visual C++ provides extended functions that offer the same functionality as the standard CRT functions but with a broader safety net to prevent common mistakes that malicious code can exploit.
Consider the venerable and widely used memcpy function, which allows the contents of one buffer to be copied to another. The standard memcpy function has the following prototype:
void *memcpy(void *dest, const void *src, size_t count);
The function has an inherent problem: The size of the destination buffer is never specified to the CRT, which means that it is not possible to implement any safety check to ensure that the destination buffer is big enough to receive the data. Any sensible developer, of course, will check that the destination size is sufficient, but experience has shown that, in the course of complex software development projects that span many releases, the responsibility for which bit of code needs to perform the check can become unclear. As a result, the check can be missed entirely, leading to a vulnerability.
The extended version of the CRT adds an _s to the name of the function and a new parameter that allows the size of the destination buffer to be specified. The CRT function then can perform a quick check that ensures that the size of the destination buffer is sufficient to accommodate the number of bytes that need to be copied from the source buffer. In many cases, this destination buffer size check will already occur in the code that calls functions like memcpy, but it is the cases where sloppy programming or maintenance mistakes have neglected to include the check that the new secure functions will catch. The modified prototype of memcpy is:
errno_t memcpy_s(void *dest, size_t sizeInBytes, const void *src, size_t count);
Notice too that the return value has been changed from a pointer that returns the address of the destination buffer in the case of memcpy to an error code in the case of memcpy_s. If the destination buffer is of insufficient size when a call to memcpy_s is made, the invalid parameter handler is invoked. By default, this will result in an access violation, but you can change the invalid parameter handler with a call to _set_invalid_parameter_handler, which takes a function pointer to the new handler function to be used.
The Secure CRT in Use
While converting to the secure CRT functions is generally a good idea, the work involved in identifying all the CRT functions that now have a secure form, finding all the instances of these functions in a large code base, and then converting them is a fairly large task. For the record, the complete list of functions with secure versions in Visual C++ 2005 is available here. This list is obviously quite long, and it would be great if there were a way for the compiler to help. Thankfully, the compiler does provide some help by deprecating the non-secure versions of most CRT functions that have a secure alternative. This means that the compiler will produce a warning each time you use a non-secure function, as shown in Figure 1.
Figure 1: Compiler Warning Related to the Use of a Deprecated Function
By working through each of these warnings, you can significantly raise the security level for a C/C++ application. In most cases, the value to pass in as the extra parameter will be obvious. For the cases where working out how long a particular buffer will be for all the different code paths seems impossible, a potential vulnerability likely has been discovered, which is one of the main points of the exercise.
Improve Application Security in 2006
Although it may seem like a tedious and boring exercise, make improving the security of your applications via the employment of the new CRT security features a New Year's resolution that you actually keep!
About the Author
Nick Wienholt is an independent Windows and .NET consultant based in Sydney, Australia. He is the author of Maximizing .NET Performance from Apress, and specializes in system-level software architecture and development with a particular focus on performance, security, interoperability, and debugging. Nick can be reached at NickW@dotnetperformance.com.