Skip to content

Commit

Permalink
Support for building with sanitizers (#149)
Browse files Browse the repository at this point in the history
* Add support for building with sanitizers

* CI Enable building with ASAN+UBSAN sanitizers in Debug builds

* Add directions to README

* Better CI reporting and matrix for sanitizer runs
  • Loading branch information
ekilmer authored Mar 16, 2021
1 parent 401743f commit 4286f10
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Enable ASan+UBSan Sanitizers
if: matrix.build-type == 'Debug'
run: |
echo "SANITIZER_FLAG=-DPEPARSE_USE_SANITIZER=Address,Undefined" >> $GITHUB_ENV
- name: build
env:
CC: ${{ matrix.compiler.CC }}
Expand All @@ -55,6 +59,7 @@ jobs:
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \
-DPEPARSE_ENABLE_TESTING=ON \
${SANITIZER_FLAG} \
..
cmake --build .
- name: test
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ You can build the (catch2-based) tests by adding `-DPEPARSE_ENABLE_TESTING=ON` d

To run the full test suite with the [Corkami test suite](https://github.com/corkami/pocs/tree/master/PE), you must clone the submodule with `git submodule update --init`.

## Building with Sanitizers

If you are familiar with C++ sanitizers and any specific development environment requirements for them (compiler, instrumented standard library, etc.), you can choose to compile with any of the following sanitizers: `Address`, `HWAddress`, `Undefined`, `Memory`, `MemoryWithOrigins`, `Leak`, `Address,Undefined`.

For example, to compile with both `Address` and `Undefined` sanitizers, use the following (recommended for development and testing, and tested in CI):

```bash
mkdir build-san
cd build-san

cmake -DCMAKE_BUILD_TYPE=Debug -DPEPARSE_ENABLE_TESTING=ON -DPEPARSE_USE_SANITIZER=Address,Undefined ..
cmake --build .
```

## Using the library

Once the library is installed, linking to it is easy! Add the following lines in your CMake project:
Expand Down
3 changes: 3 additions & 0 deletions cmake/compilation_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include("cmake/sanitizers.cmake")
process_sanitizer(PEPARSE)

if (MSVC)
list(APPEND DEFAULT_CXX_FLAGS /W4 /analyze)

Expand Down
173 changes: 173 additions & 0 deletions cmake/sanitizers.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Enable C/C++ sanitizers in your CMake project by `include`ing this file
# somewhere in your CMakeLists.txt.
#
# Example:
#
# include("cmake/sanitizers.cmake")
# process_sanitizer(MY_PROJECT)
#
# Example CMake configuration usage for `MY_PROJECT`:
#
# cmake -Bbuild-asan-ubsan -H. -DMY_PROJECT_USE_SANITIZER=Address,Undefined
#
# where "MY_PROJECT" can by any arbitrary text that will be prepended to some
# expected CMake variables--see below.
#
# This file expects the following variables to be set during CMake
# configuration, where the prefix is set by the caller of `process_sanitizer`:
#
# - *_USE_SANITIZER
# - A string value that is one of the following:
# - Address
# - HWAddress
# - Memory
# - MemoryWithOrigins
# - Undefined
# - Thread
# - DataFlow
# - Leak
# - Address,Undefined
#
# - *_OPTIMIZE_SANITIZED_BUILDS
# - A boolean value to set whether a higher optimization is used in debug
# builds
#
# - *_BLACKLIST_FILE
# - A filepath to a sanitizer blacklist file.


function(append value)
foreach(variable ${ARGN})
set(${variable}
"${${variable}} ${value}"
PARENT_SCOPE)
endforeach(variable)
endfunction()


macro(append_common_sanitizer_flags prefix)
if (NOT MSVC)
# Append -fno-omit-frame-pointer and turn on debug info to get better
# stack traces.
append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-fno-optimize-sibling-calls" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-gline-tables-only" CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZE_REL)
# Use -O1 even in debug mode, otherwise sanitizers slowdown is too large.
if (${prefix}_OPTIMIZE_SANITIZED_BUILDS)
message(STATUS "Optimizing sanitized Debug build")
append("-O1" CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
endif()
else()
# Keep frame pointers around.
append("/Oy-" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Always ask the linker to produce symbols with asan.
append("/Zi" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Workaround for incompatible (warning-producing) default CMake flag
# https://docs.microsoft.com/en-us/cpp/sanitizers/asan-known-issues
# https://gitlab.kitware.com/cmake/cmake/-/issues/19084
string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
append("/debug" CMAKE_EXE_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS)
endif()
endmacro()


# Main logic
macro(process_sanitizer prefix)

# Add options for the project to use sanitizers
option(${prefix}_USE_SANITIZER "Enable building with sanitizer support. Options are: Address, HWAddress, Memory, MemoryWithOrigins, Undefined, Thread, DataFlow, Leak, 'Address,Undefined'" false)
if (UNIX)
option(${prefix}_OPTIMIZE_SANITIZED_BUILDS "Optimize builds that use sanitization" false)
option(${prefix}_BLACKLIST_FILE "Path to blacklist file for sanitizers" "")
option(${prefix}_USE_SANITIZE_COVERAGE "Set for libFuzzer-required instrumentation, no linking." false)
endif()

if (${prefix}_USE_SANITIZER)
if(UNIX)

if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER STREQUAL "HWAddress")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=hwaddress" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER MATCHES "Memory(WithOrigins)?")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
if(${prefix}_USE_SANITIZER STREQUAL "MemoryWithOrigins")
message(STATUS "Building with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(STATUS "Building with Memory sanitizer")
endif()

elseif(${prefix}_USE_SANITIZER STREQUAL "Undefined")
message(STATUS "Building with Undefined sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER STREQUAL "Thread")
message(STATUS "Building with Thread sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER STREQUAL "DataFlow")
message(STATUS "Building with DataFlow sanitizer")
append("-fsanitize=dataflow" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER STREQUAL "Leak")
message(STATUS "Building with Leak sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

elseif(${prefix}_USE_SANITIZER STREQUAL "Address,Undefined"
OR ${prefix}_USE_SANITIZER STREQUAL "Undefined,Address")
message(STATUS "Building with Address, Undefined sanitizers")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

else()
message(
FATAL_ERROR "Unsupported value of ${prefix}_USE_SANITIZER: '${${prefix}_USE_SANITIZER}'")
endif()
elseif(MSVC)
if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(FATAL_ERROR "This sanitizer is not yet supported in the MSVC environment: '${${prefix}_USE_SANITIZER}'")
endif()
else()
message(FATAL_ERROR "${prefix}_USE_SANITIZER is not supported on this platform.")
endif()

# If specified, use a blacklist file
if (EXISTS "${${prefix}_BLACKLIST_FILE}")
message(STATUS "Using sanitizer blacklist file: ${${prefix}_BLACKLIST_FILE}")
append("-fsanitize-blacklist=${${prefix}_BLACKLIST_FILE}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()

# Set a once non-default option for more detection
if (${prefix}_USE_SANITIZER MATCHES "(Undefined,)?Address(,Undefined)?")
if (UNIX)
append("-fsanitize-address-use-after-scope" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()

# Set for libFuzzer-required instrumentation, no linking.
if (${prefix}_USE_SANITIZE_COVERAGE)
message(STATUS "Setting up sanitizer for coverage support with 'fuzzer-no-link'")
append("-fsanitize=fuzzer-no-link" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()
endmacro()

0 comments on commit 4286f10

Please sign in to comment.