C-Based Toolchain Hardening
C-Based Toolchain Hardening is a technical guide to creating executables which have increased reliability and security. This article will examine Microsoft and GCC toolchains for the C, C++ and Objective C languages. It will guide you through the steps you should take to create executables with firmer defensive postures and increased integration with the available platform security. Effectively configuring the toolchain also means your project will enjoy a number of benefits during development, including enhanced warnings and static analysis, and self-debugging code.
There are four areas to be examined when hardening the toolchain: configuration, preprocessor, compiler, and linker. Nearly all areas are overlooked or neglected when setting up a project. The neglect appears to be pandemic, and it applies to nearly all projects including Auto-configured projects, Makefile-based, Eclipse-based, Visual Studio-based, and Xcode-based.
The article will also detail steps which quality assurance personnel can perform to ensure third party code meets organizational standards. Many organizations have Security Testing and Evaluation (ST&E) programs or operate in the US Federal arena where supply chain audits are necessary. If you audit a program with a lot of gaps, it could indicate the company providing the binaries does not have a mature engineering process or has gaps in its internal QA processes. For those who lack mature quality assurance or acceptance and testing criteria, then this article will also provide helpful suggestions.
Proper use of auditing tools such as checksec and readelf on Linux and BinScope on Windows means source code will be rarely needed for some portions of an audit. Lack of source code clears a number of legal obstacles in the acceptance testing process since NDAs or other agreements may not be required.
For those who are not aware, the US's DMCA (PUBLIC LAW 105–304) has proper exceptions for reverse engineering and security testing and evaluation. The RE exemption is in Section 1205 (f) REVERSE ENGINEERING; and the ST&E exemption is in Section 1205 (i) SECURITY TESTING. If you don't need source code access, then you can decompile, re-engineer, and test without the need for consent or worry of legal reprisals.
Compiler writers and others involved in the toolchain work hard to help projects deliver reliable, secure, efficient programs. Their efforts are available to you at all stages of our engineering processes - from project configuration and preprocessing to compiling and linking. For example, its non-trivial to ensure line numbers in source files match debug information due to the processing of #include, considering projects can be configured with no macros, the DEBUG macro, or the NDEBUG macro. Since users expect trouble free and safe code, it would be wise if you used all tools available in our war chests.
Both GCC and Visual Studio have static analysis capabilities to help find mistakes early in the development process. In addition, both tool chains have options to produce a hardened executable by taking advantage of the security offered by the platform. When compiling and linking, it would be wise to use both static analysis and hardening techniques since they will help you obtain stability and security.
The built in static analysis capabilities of GCC and Visual Studio are usually sufficient to ensure proper API usage and catch a number of mistakes such as using an uninitialized variable or comparing a negative signed int and a positive unsigned int. As a concrete example, (and for those not familiar with C/C++ promotion rules), a warning will be issued if a signed integer is promoted to an unsigned integer and then compared because a side effect is -1 > 1 after promotion!
GCC and Visual Studio will not currently catch, for example, SQL injections and other tainted data usage. For that, you will need a tool designed to perform data flow analysis and taint analysis.
Some in the development community resist static analysis or refute its results. For example, when static analysis warned the kernel's sys_prctl was comparing an unsigned value against less than zero, Jesper Juhl offered a patch to clean up the code. Linus Torvalds howled “No, you don't do this... GCC is crap.” For the full discussion, see [PATCH] Don't compare unsigned variable for <0 in sys_prctl() from the Linux Kernel mailing list.