Skip to main content

Integrating Sanitizer tools into CMake builds

·990 words·5 mins
Code Cmake C/Cpp
Table of Contents

NOTE: The content here has been superseeded by a better implementation outlined here:

CMake script suite - Making CMake work for you
·1051 words·5 mins
Code Cmake
Built over some time now, is a fair sized suite of tools and utilities to improve the CMake compilation experience.

The Sanitizers
#

Sanitizers are tools that perform checks during a program’s runtime and returns issues, and as such, along with unit testing, code coverage and static analysis, is another tool to add to the programmers toolbox. And of course, like the previous tools, are tragically simple to add into any project using CMake, allowing any project and developer to quickly and easily use.

A quick rundown of the tools available, and what they do:

  • LeakSanitizer detects memory leaks, or issues where memory is allocated and never deallocated, causing programs to slowly consume more and more memory, eventually leading to a crash.
  • AddressSanitizer is a fast memory error detector. It is useful for detecting most issues dealing with memory, such as:
    • Out of bounds accesses to heap, stack, global
    • Use after free
    • Use after return
    • Use after scope
    • Double-free, invalid free
    • Memory leaks (using LeakSanitizer)
  • ThreadSanitizer detects data races for multi-threaded code.
  • UndefinedBehaviourSanitizer detects the use of various features of C/C++ that are explicitly listed as resulting in undefined behaviour. Most notably:
    • Using misaligned or null pointer.
    • Signed integer overflow
    • Conversion to, from, or between floating-point types which would overflow the destination
    • Division by zero
    • Unreachable code
  • MemorySanitizer detects uninitialized reads.

Adding to CMake
#

As these sanitizers usually are incompatible with one another, the most logical way to implement them is to integrate them as CMake build types, so the programs are built optimally for the sanitizer. To give optimal results, these build types ignore all other compiler flags given by the user, and apply to both the C and C++ languages.

# Build Types
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}
    CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel tsan asan lsan msan ubsan"
    FORCE)

# ThreadSanitizer
set(CMAKE_C_FLAGS_TSAN
    "-fsanitize=thread -g -O1"
    CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds."
    FORCE)
set(CMAKE_CXX_FLAGS_TSAN
    "-fsanitize=thread -g -O1"
    CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds."
    FORCE)

# AddressSanitize
set(CMAKE_C_FLAGS_ASAN
    "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
    CACHE STRING "Flags used by the C compiler during AddressSanitizer builds."
    FORCE)
set(CMAKE_CXX_FLAGS_ASAN
    "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
    CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds."
    FORCE)

# LeakSanitizer
set(CMAKE_C_FLAGS_LSAN
    "-fsanitize=leak -fno-omit-frame-pointer -g -O1"
    CACHE STRING "Flags used by the C compiler during LeakSanitizer builds."
    FORCE)
set(CMAKE_CXX_FLAGS_LSAN
    "-fsanitize=leak -fno-omit-frame-pointer -g -O1"
    CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds."
    FORCE)

# MemorySanitizer
set(CMAKE_C_FLAGS_MSAN
    "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2"
    CACHE STRING "Flags used by the C compiler during MemorySanitizer builds."
    FORCE)
set(CMAKE_CXX_FLAGS_MSAN
    "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2"
    CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds."
    FORCE)

# UndefinedBehaviour
set(CMAKE_C_FLAGS_UBSAN
    "-fsanitize=undefined"
    CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds."
    FORCE)
set(CMAKE_CXX_FLAGS_UBSAN
    "-fsanitize=undefined"
    CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds."
    FORCE)

Building with any of the above targets, using the option during CMake’s generation such as -DCMAKE_BUILD_TYPE=asan would build all libraries and executables with the diagnostic tooling for AddressSanitizer linked in, and then running the programs would then be able to output messages such as in AddressSanitizer’s case:

=================================================================
==20865==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000044 at 
pc 0x561435ccba1f bp 0x7ffd162bbce0 sp 0x7ffd162bbcd0
READ of size 4 at 0x614000000044 thread T0
    #0 0x561435ccba1e in main ../src/asan_fail.cpp:4
    #1 0x7fe020070f49 in __libc_start_main (/usr/lib/libc.so.6+0x20f49)
    #2 0x561435ccb8e9 in _start (/workspace/StableCoder_ci-tests_master/build/asanFail+0x8e9)

0x614000000044 is located 4 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
    #0 0x7fe020dd55a9 in operator delete[](void*) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:128
    #1 0x561435ccb9ec in main ../src/asan_fail.cpp:3
    #2 0x7fe020070f49 in __libc_start_main (/usr/lib/libc.so.6+0x20f49)

previously allocated by thread T0 here:
    #0 0x7fe020dd4669 in operator new[](unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:82
    #1 0x561435ccb9dc in main ../src/asan_fail.cpp:2
    #2 0x7fe020070f49 in __libc_start_main (/usr/lib/libc.so.6+0x20f49)

SUMMARY: AddressSanitizer: heap-use-after-free ../src/asan_fail.cpp:4 in main
Shadow bytes around the buggy address:
  0x0c287fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c287fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c287fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c287fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c287fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c287fff8000: fa fa fa fa fa fa fa fa[fd]fd fd fd fd fd fd fd
  0x0c287fff8010: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c287fff8020: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c287fff8030: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
  0x0c287fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c287fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==20865==ABORTING

And again, because these are integrated into CMake, it become trivial to run these runtime tests on one’s local machine, or to automate them in a CI pass.

alt text

Source Code
#

StableCoder/cmake-scripts

Easy-to-add enhancements for any C/C++ CMake project. Including AFL fuzzing, code-coverage, Thread/Address/Leak/Address/undefined sanitizer instrumentation, compilation of GLSL shaders and more.

CMake
527
62