Secure C++: Checked Iterators for Safer STL Coding
The C++ language, STL, and the Standard C++ Libraries are much more modern and robust than the C language and C Runtime Libraries. As the security of software is strongly tied to its robustness, it comes as no surprise that the security issues with Standard C++ are much less severe than those of C and the CRT. Nevertheless, a few exploits are possible with Standard C++, and a new functionality in Visual C++ closes these loop-holes.
STL makes heavy use of iterators for the transversal of collections. Improper use of an iterator can occur in two main ways:
- Using an iterator that has been invalidated by collection modification
- Using an iterator to attempt to access an element outside the bounds of a collection
The code below shows both of these problems:
//use an invalid iterator vector <int> vec; vec.push_back(1); std::vector<int>::iterator it = vec.begin(); vec.push_back(2); std::cout<<*it; //attempt to access outside iterator bounds vector <int> emptyVec; std::vector<int>::iterator emptyIt = emptyVec.begin(); int i = *emptyIt;
In the debug build of a Visual C++ 2005 application, assertions will catch both of these conditions, making tracking down these coding errors extremely easy. In a release version, the use of an invalid iterator will not be caught, but an attempt to access data outside of a collection will be caught. The debug build support is delivered through a part of Visual C++ referred to as Debug Iterator Support, and it is implemented via checks in the runtime library source code. Debug Iterator Support is on by default in a debug build, but you can disable it by defining the symbol _HAS_ITERATOR_DEBUGGING to have a value of zero.
In contrast to Debug Iterator Support, which is primarily focused on code correctness, checked iterators, which are a release build concept, are primarily focused on preventing security problems with a running application. Rather than raise an assertion, checked iterators generate a runtime error that will terminate the program by default. You have two options for dealing with an error caused by a checked iterator:
- Make a call to the _set_invalid_parameter_handler so that an invalid parameter handler can be set.
- Define _SECURE_SCL_THROWS as 1, which will result in an exception being thrown.
To force Visual C++ to behave according to the C++ standard and not check iterators, you can define _SECURE_SCL as zero. (If you're concerned about where all this deviation from standard C++ is heading, this article addresses that topic later.)
Checked iterators apply to operator, front, and back methods of most STL containers that actually have these methods. Because algorithms play such an important part of the STL, it was crucial for them to be integrated with the checked iterators work. One of the key principals of Microsoft's security push was for security to be on by default, and this is the case with both checked iterators (as covered above) and the STL algorithm's use of these iterators. For an application that uses checked iterators, all calls to standard algorithm functions will result in a secure function being called. This means that a call to, say, std::merge will be forwarded to stdext::checked_merge. The checked_ versions of the STL algorithms prevent an unchecked iterator from being used, which can be important for catching unchecked iterators that can originate from third-party components and those that have not been recompiled with Visual C++ 2005. Code that attempts to pass an unchecked iterator to a checked algorithm will not compile.
Visual C++ Security Enhancements and Language Standards
Organizations have so abused the statement "working with the relevant standards bodies" to incorporate their extensions and modifications that it now fits in the same euphemism category as "assisting police with their enquiries." This particularly holds true for Microsoft, which has a long history of ignoring and subverting standardization processes for their own commercial gain. To be fair, this trend has diminished of late, and Microsoft is an important and instrumental player in a number of standards bodies. As the company making the largest investment in C++, being a player in the standards game is particularly important.
Rather than just taking Microsoft at their word, it is worth looking at the standard compliance efforts from independent sources. The most recent tests of C++ library conformance available come from Dinkunware. They show that the last version of Visual C++ tested was version 7, and it scored a compliance rating of more than ninety-nine percent.
C++/CLI is now an ECMA standard, which means that (as with standard C++) any compiler vendor can go off and build a C++/CLI language compiler. A particular C++/CLI compiler might target the CLR or an entirely different environment, such as a Java runtime. An objection to the C++/CLI standard claimed that C++/CLI is so different from standard C++ that it should have a different name. The core argument was that "C++/CLI has effectively evolved into a language which is almost, but not quite, entirely unlike C++ as we know it. Significant differences can be found in syntax, semantics, idioms, and underlying object model." The objection was not upheld, and C++/CLI is now a full standard as of December 2005.
Microsoft is currently working the security modification of the CRT through the C standards process. The latest document in this process is Extensions to the C Library Part I: Bounds-Checking Interfaces. If and when these changes are ratified and any modifications to the Visual C++ 2005 _s functions are incorporated back into Visual C++, it will be possible to write portable and more secure code that uses the CRT.
Finally, nothing in Visual C++ 2005 forces the use of these secure extensions or C++/CLI. If you're happy to set the various #defines (or ignore the compiler warnings in some cases), pure, unadulterated standard C/C++ is possible.
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.