My previous two articles looked at improvements in the C Runtime Library and Standard C++ Library that make it easier to write secure code. The key to real security is to have multiple layers of defense. Relying solely on runtime library improvements to guard against coding patterns that can lead to buffer overruns and other security vulnerabilities is not sufficient. This article examines the Static Code Analyzer that ships with Visual Studio Team System (VSTS), Developer Edition, and explains how it can detect common security issues in native C/C++ code.
C and C++ have a proud history of static code analysis to improve code quality. In fact, Gimpel Software, the makers of PC-lint, a third-party code analysis tool, claims that its release in 1985 makes it “the longest continuously advertised software tool in human history.” Microsoft has dabbled in code analysis tools for a long time. FXCop, a tool for managed code inspection, is just the latest in a series of analysis tool releases that never became part of the Visual C++ or Visual Studio core. Now, with VSTS, a large package consisting of Visual Studio and a series of job-specific toolkits, Microsoft has made code analysis tools a first-class offering.
VSTS is a huge package, available in three job-specific editions (Architect, Developer, and Tester) separately or as one package called Visual Studio Team System Suite. A collaboration, change management, and project management server product called Team Foundation Server (TFS) underpins the whole VSTS effort. TFS is due for release this month (March 2006). Code analysis is only a small part of the VSTS/TFS picture (for more information, check out the VSTS home page).
Static Code Analyzer
For native C/C++ projects, the primary form of code analysis is static code analysis. In contrast to dynamic code analysis that monitors an application as it runs and looks at patterns of data and function usage, static code analysis involves inspecting source code and looking for patterns that could indicate problems despite the fact that they may be legal syntax and pass through the compiler without error or warning. PC-lint is an example of a static code analyzer, whereas a profiler is a dynamic analysis tool.
The classic problem with any code analysis tool is that it is generally too easy during the hurly-burly of a software project to skip the analysis and rely solely on the compiler as a test of code correctness. In the pre-dotcom boom days, many C++ shops had a learned elder who held young pup developers to a high standard and could enforce code analysis checks. These days, the senior technical person in an organization tends to have a strange, grandiose title that ends with the word architect and more likely than not, has a “coding” background in markup and script. The Team System code analysis tools for C/C++ harken back to the days of the higher standards by integrating the analysis into the compilation process.
To enable code analysis in VSTS, Developer Edition, bring up the Project Properties and go to the Code Analysis tree node (see Figure 1). From there, enabling checking is a simple matter of changing the Enable Code Analysis for C/C++ setting from No to Yes.
Figure 1. Enabling C/C++ Code Analysis
With Enable Code Analysis for C/C++ set to Yes, Visual Studio will perform code analysis as part of the build process. For a build that is occurring outside Visual Studio, you can accomplish the same code analysis using the /analyze command line switch of cl.exe.
You also can enforce code analysis by using a check-in policy in TFS that prevents code that does not pass analysis from entering the central code repository.
Using C/C++ Analysis
The RTM version on VSTS, Developer Edition ships with 139 specific checks for C++ code (see the full list on MSDN). It currently has no extensibility mechanism for the native C++ checks, but Eric Jarvi (Software Design Engineer in Test, Visual Studio – Profiler) hints that it will be available in a future VSTS version in a blog post. With the C/C++ analysis enabled, failed checks will appear as warnings during the build process. To enable or disable specific warnings, you can use the same #pragma warning enable and #pragma warning disable that works with standard warnings.
Look at a brief example. The following code will pass all the normal checks that the C++ compiler enforces:
int _tmain(int argc, _TCHAR* argv)
//security for file
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = &sd;
HANDLE logFileHandle = ::CreateFile(_T(“c:log.txt”),
GENERIC_WRITE, FILE_SHARE_WRITE, &sa,
FILE_ATTRIBUTE_NORMAL, NULL, NULL);
//write log here
Notice the superficial effort to set some sort of security token on the file; the code may pass a quick inspection that aims to check that all files created by an application have an appropriate DACL applied. However, a more detailed inspection reveals that the SetSecurityDescriptorDacl is called with a NULL parameter, which means that no effective security settings are applied to the generated file. In a large and complex application, this security issue could easily be missed, resulting in a vulnerability that a malicious attacker may exploit.
If this application is compiled with C/C++ code analysis enabled, warning C6248: setting a SECURITY_DESCRIPTOR’s DACL to NULL will result in an unprotected object will be displayed, and as with standard C++ warnings, clicking on the warning in the Build Output window will highlight the line of code related to the warning. Figure 2 shows this in action.
Figure 2. Code Analysis in Action
Another Technique to Ensure Secure Applications
As with any code correctness and security measure, the native C/C++ code analysis tools are simply another technique to help a developer cut better code. They do not replace the need for developer training, code reviews, threat modeling, and all the other practices that combine to ensure that a robust, correct, and secure application ships at the end of the day.
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 [email protected].