Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for building with sanitizers #149

Merged
merged 4 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()