Make your C++ code more safer by enabling the native compiler Runtime Checks.

Runtime checks in C++ refer to mechanisms or tools used to detect errors, vulnerabilities, or unexpected behavior in a program while it is executing. These checks are performed dynamically during runtime rather than at compile-time and can help identify issues that may not be apparent during static analysis or code review.

In the context of software development the term “sanitizer” refers to this kind of runtime checks. Sanitizers operate by instrumenting code with additional checks or tracking mechanisms to detect common programming mistakes or security vulnerabilities. When an issue is detected, the sanitizer typically provides feedback or generates diagnostic information to assist developers in understanding and resolving the problem.

However, not all C++ developers are aware that almost all C++ compilers provide native runtime checks. Clang, GCC, and the Microsoft compiler offer advanced sanitizers to enhance the safety of your C++ code.

The Microsoft compiler enable by default these runtime cheks in debug mode. However, for clang and gcc you have to enable them manually.

Here are a list of the most popular sanitizers:

  1. AddressSanitizer (ASan): AddressSanitizer detects memory corruption errors such as buffer overflows, use-after-free, and out-of-bounds accesses. It works by allocating a shadow memory region alongside the program’s memory and checking memory accesses against this shadow memory to detect violations.
  2. MemorySanitizer (MSan): MemorySanitizer detects the use of uninitialized memory, which can lead to undefined behavior and security vulnerabilities. It tracks the initialization status of memory locations at runtime and reports any attempts to use uninitialized memory.
  3. UndefinedBehaviorSanitizer (UBSan): UndefinedBehaviorSanitizer detects undefined behavior in C and C++ code, such as integer overflows, null pointer dereferences, and signed integer overflow. It instruments the code to detect and report instances of undefined behavior at runtime.
  4. ThreadSanitizer (TSan): ThreadSanitizer detects data races and other concurrency-related bugs in multithreaded C and C++ code. It works by analyzing the program’s execution to identify conflicting accesses to shared memory by different threads.
  5. DataFlowSanitizer (DFSan): DataFlowSanitizer detects taint-style vulnerabilities by tracking the flow of data through the program and identifying potentially dangerous operations involving tainted data, such as SQL injection or command injection.

Clang

Clang provides a list of options to customize the runtime checks as described here.

For example:

% clang++ -fsanitize=signed-integer-overflow,null,alignment -fno-sanitize-recover=null -fsanitize-trap=alignment a.cc

The program will continue execution after signed integer overflows, exit after the first invalid use of a null pointer, and trap after the first use of misaligned pointer.

Using Clang sanitizers can help identify bugs and vulnerabilities early in the development process, before they manifest as runtime errors or security vulnerabilities in production code. They are particularly valuable for finding difficult-to-diagnose issues that may not be caught by traditional testing methods.

Microsoft C++ Compiler

The microsoft compiler provides also many runtime checks as described here.

When you debug a program that has run-time checks enabled, the default action is for the program to stop and break to the debugger when a run-time error occurs. You can change this default behavior for any run-time check. For more information, see Managing Exceptions with the Debugger.

GCC:

GCC supports a number of command-line options that control adding run-time instrumentation to the code it normally generates. For example, one purpose of instrumentation is collect profiling statistics for use in finding program hot spots, code coverage analysis, or profile-guided optimizations. Another class of program instrumentation is adding run-time checking to detect programming errors like invalid pointer dereferences or out-of-bounds array accesses, as well as deliberately hostile attacks such as stack smashing or C++ vtable hijacking. A complete list of sanitizers are available here.

How we can establish a habit of utilizing C++ sanitizers?

  1. Learn about Sanitizers: Educate yourself and your team about the different types of sanitizers available in C++, such as AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), and ThreadSanitizer (TSan). Understand how each sanitizer detects specific types of bugs and vulnerabilities in your code.
  2. Integrate Sanitizers into Your Build Process: Make it a standard practice to enable sanitizers during the build process of your C++ projects. Modify your build scripts or configuration files (e.g., CMakeLists.txt) to include compiler flags that enable the desired sanitizers.
  3. Start with a Small Project: Begin by incorporating sanitizers into a small, non-critical project or module within your codebase. This allows you to become familiar with the process without overwhelming yourself or your team.
  4. Enable Sanitizers for Testing and Debugging: Make it a habit to run your tests and debug builds with sanitizers enabled. This helps catch bugs early in the development cycle, making them easier and cheaper to fix.
  5. Review Sanitizer Output Regularly: Pay attention to the output generated by the sanitizers during the build process. Take time to understand the detected issues and their root causes. Use this feedback to improve your code and prevent similar issues in the future.
  6. Incorporate Sanitizers into Code Reviews: Encourage team members to enable sanitizers during code reviews. Discuss any issues detected by the sanitizers and work together to address them.
  7. Provide Training and Support: Offer training sessions or workshops to help team members understand how to use sanitizers effectively. Provide support and guidance as needed, especially for those who are new to using sanitizers.
  8. Document Best Practices: Document best practices for using sanitizers within your organization. Include guidelines on when and how to enable sanitizers, how to interpret sanitizer output, and how to address common issues detected by sanitizers.
  9. Set Goals and Track Progress: Set goals for increasing the usage of sanitizers within your team or organization. Track progress regularly and celebrate milestones to reinforce the habit of using sanitizers.
  10. Continuous Improvement: Continuously evaluate and improve your processes for using sanitizers. Solicit feedback from team members and adapt your approach based on lessons learned.

By following these steps and making the use of sanitizers a standard practice within your team, you can develop a habit of using C++ sanitizers effectively to improve the quality and safety of your code.