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

Specify sanitizers using IGN_SANITIZERS cmake variable #210

Merged
merged 9 commits into from
Apr 1, 2022
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ Replace `/path/to/install/dir` to whatever directory you want to install this pa

# Usage

This library is used internally by the ignition projects. See other ignition projects for examples of how this gets used.
Documentation can be accessed at https://ignitionrobotics.org/libs/cmake
[Examples](examples/) are available in this repository.
[Tutorials](tutorials/) are also available in this repository.

# Folder Structure

Expand Down
1 change: 1 addition & 0 deletions cmake/IgnCMake.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ include(IgnSetCompilerFlags)
include(IgnConfigureBuild)
include(IgnImportTarget)
include(IgnPkgConfig)
include(IgnSanitizers)

#============================================================================
# Native cmake modules
Expand Down
218 changes: 218 additions & 0 deletions cmake/IgnSanitizers.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#
# Copyright (C) 2018-2022 by George Cave - [email protected]
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

# Original code https://raw.githubusercontent.com/StableCoder/cmake-scripts/main/sanitizers.cmake

include(CheckCXXSourceCompiles)

set(IGN_SANITIZER ""
CACHE STRING
"Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined', CFI"
)

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

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

function(test_san_flags return_var flags)
set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET TRUE)
unset(${return_var} CACHE)
set(FLAGS_BACKUP ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${flags}")
check_cxx_source_compiles("int main() { return 0; }" ${return_var})
set(CMAKE_REQUIRED_FLAGS "${FLAGS_BACKUP}")
set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}")
endfunction()

if(IGN_SANITIZER)
append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

unset(SANITIZER_SELECTED_FLAGS)

if(UNIX)
if(IGN_SANITIZER MATCHES "([Aa]ddress)")
# Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope
message(STATUS "Testing with Address sanitizer")
set(SANITIZER_ADDR_FLAG "-fsanitize=address")
test_san_flags(SANITIZER_ADDR_AVAILABLE ${SANITIZER_ADDR_FLAG})
if(SANITIZER_ADDR_AVAILABLE)
message(STATUS " Building with Address sanitizer")
append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR
"Address sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()

if(IGN_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)")
# Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2
set(SANITIZER_MEM_FLAG "-fsanitize=memory")
if(IGN_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)")
message(STATUS "Testing with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" SANITIZER_MEM_FLAG)
else()
message(STATUS "Testing with Memory sanitizer")
endif()
test_san_flags(SANITIZER_MEM_AVAILABLE ${SANITIZER_MEM_FLAG})
if(SANITIZER_MEM_AVAILABLE)
if(IGN_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)")
message(STATUS " Building with MemoryWithOrigins sanitizer")
else()
message(STATUS " Building with Memory sanitizer")
endif()
append("${SANITIZER_MEM_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFL is not being used here, so we can remove this if block. Here and elsewhere below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I'm in favor of leave it in place just in the case that someone (or even us in the future) is using that support now that we have it implemented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'm okay with that, but I haven't tested it. Not sure if you've had a chance.

append_quoteless(AFL_USE_MSAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR
"Memory [With Origins] sanitizer not available for ${CMAKE_CXX_COMPILER}"
)
endif()
endif()

if(IGN_SANITIZER MATCHES "([Uu]ndefined)")
message(STATUS "Testing with Undefined Behaviour sanitizer")
set(SANITIZER_UB_FLAG "-fsanitize=undefined")
if(EXISTS "${BLACKLIST_FILE}")
append("-fsanitize-blacklist=${BLACKLIST_FILE}" SANITIZER_UB_FLAG)
endif()
test_san_flags(SANITIZER_UB_AVAILABLE ${SANITIZER_UB_FLAG})
if(SANITIZER_UB_AVAILABLE)
message(STATUS " Building with Undefined Behaviour sanitizer")
append("${SANITIZER_UB_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
append_quoteless(AFL_USE_UBSAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR
"Undefined Behaviour sanitizer not available for ${CMAKE_CXX_COMPILER}"
)
endif()
endif()

if(IGN_SANITIZER MATCHES "([Tt]hread)")
message(STATUS "Testing with Thread sanitizer")
set(SANITIZER_THREAD_FLAG "-fsanitize=thread")
test_san_flags(SANITIZER_THREAD_AVAILABLE ${SANITIZER_THREAD_FLAG})
if(SANITIZER_THREAD_AVAILABLE)
message(STATUS " Building with Thread sanitizer")
append("${SANITIZER_THREAD_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
append_quoteless(AFL_USE_TSAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}"
)
endif()
endif()

if(IGN_SANITIZER MATCHES "([Ll]eak)")
message(STATUS "Testing with Leak sanitizer")
set(SANITIZER_LEAK_FLAG "-fsanitize=leak")
test_san_flags(SANITIZER_LEAK_AVAILABLE ${SANITIZER_LEAK_FLAG})
if(SANITIZER_LEAK_AVAILABLE)
message(STATUS " Building with Leak sanitizer")
append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
append_quoteless(AFL_USE_LSAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}"
)
endif()
endif()

if(IGN_SANITIZER MATCHES "([Cc][Ff][Ii])")
message(STATUS "Testing with Control Flow Integrity(CFI) sanitizer")
set(SANITIZER_CFI_FLAG "-fsanitize=cfi")
test_san_flags(SANITIZER_CFI_AVAILABLE ${SANITIZER_CFI_FLAG})
if(SANITIZER_CFI_AVAILABLE)
message(STATUS " Building with Control Flow Integrity(CFI) sanitizer")
append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS)

if(AFL)
append_quoteless(AFL_USE_CFISAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR
"Control Flow Integrity(CFI) sanitizer not available for ${CMAKE_CXX_COMPILER}"
)
endif()
endif()

message(STATUS "Sanitizer flags: ${SANITIZER_SELECTED_FLAGS}")
test_san_flags(SANITIZER_SELECTED_COMPATIBLE ${SANITIZER_SELECTED_FLAGS})
if(SANITIZER_SELECTED_COMPATIBLE)
message(STATUS " Building with ${SANITIZER_SELECTED_FLAGS}")
append("${SANITIZER_SELECTED_FLAGS}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(
FATAL_ERROR
" Sanitizer flags ${SANITIZER_SELECTED_FLAGS} are not compatible.")
endif()
elseif(MSVC)
if(IGN_SANITIZER MATCHES "([Aa]ddress)")
message(STATUS "Building with Address sanitizer")
append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)

if(AFL)
append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER
CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(
FATAL_ERROR
"This sanitizer not yet supported in the MSVC environment: ${IGN_SANITIZER}"
)
endif()
else()
message(FATAL_ERROR "IGN_SANITIZER is not supported on this platform.")
endif()

endif()
28 changes: 28 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,31 @@ if (UNIX)
set_tests_properties(${TEST_NAME} PROPERTIES
ENVIRONMENT "${_env_vars}")
endif()

if(UNIX)
# Test for the sanitizers example
set(sanitizer_compiler_log ${CMAKE_BINARY_DIR}/examples/Sanitizers-prefix/src/Sanitizers-stamp/Sanitizers-build-out.log)
ExternalProject_Add(
Sanitizers

DEPENDS FAKE_INSTALL
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/sanitizers"
LOG_BUILD ON
LOG_CONFIGURE ON
# BUILD_ALWAYS needed since cmake doesn't notice when
# example files change.
# See alternate approach in a2113e0997c9 if this becomes too slow
BUILD_ALWAYS 1
BUILD_COMMAND ${CMAKE_COMMAND} --build . --clean-first
CMAKE_ARGS
"-DCMAKE_PREFIX_PATH=${FAKE_INSTALL_PREFIX};${example_INSTALL_DIR}"
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
"-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install/Sanitizers"
"-DIGN_SANITIZER=Address"
"-DCMAKE_CXX_FLAGS=-Wno-unused-parameter"
"-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"
TEST_COMMAND
# Multiplatform python equivalent to grep "fsanitize=address" "${sanitizer_compiler_log}"
python3 -c "exec(\"import sys\\nwith open('${sanitizer_compiler_log}') as f:sys.exit(0) if 'fsanitize=address' in f.read() else sys.exit(1)\")"
)
endif()
5 changes: 5 additions & 0 deletions examples/sanitizers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
project(ignition-sanitizers VERSION 0.1.0)
find_package(ignition-cmake2 REQUIRED)
ign_configure_project()
ign_configure_build(QUIT_IF_BUILD_ERRORS)
3 changes: 3 additions & 0 deletions examples/sanitizers/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ign_get_libsources_and_unittests(sources gtest_sources)
ign_add_executable(asanfail ${sources})
add_test(asan asanfail)
23 changes: 23 additions & 0 deletions examples/sanitizers/src/asan_fail.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2018-2022 by George Cave - [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* Original code https://raw.githubusercontent.com/StableCoder/cmake-scripts/main/example/src/asan_fail.cpp
*/

int main(int argc, char * argv[]) {
int *array = new int[100];
delete[] array;
return array[argc]; // BOOM
}
1 change: 1 addition & 0 deletions tutorials.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively.

1. \subpage install "Installation"
1. \subpage developingwithcmake "Developing with Ignition CMake"
1. \subpage sanitizersbuilds "Sanitizers Builds"

## License

Expand Down
22 changes: 3 additions & 19 deletions tutorials/developing_with_ign-cmake.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,10 @@ To change the build system type, set the CMake flag:
-GNinja
```

### Address sanitizer (ASan)
### Build sanitizers

The `gcc` and `clang` compilers have a set of flags to generate instrumented builds for detecting memory leaks.

By default, address sanitizer is *not used*.

To enable address sanitizer, set all of the following flags:

```
-DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize=leak -g"
-DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak -g"
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
-DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
```

This will report if memory is leaked during execution of binaries or tests.

More information about address santizier can be found in the [ASan documentation](https:/google/sanitizers/wiki/AddressSanitizer).

Note: Address sanitizer may have an impact on the performance of execution.
`IGN_SANITIZER` CMake parameter can be used with different compilers to support the detection of different problems in the code.
[Check the documentation for `IGN_SANITIZER` flag](ign_cmake_sanitizers.md)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add ign_cmake_sanitizers.md to tutorials.md.in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem we have is that the ign_cmake_sanitizers is not really a tutorial but more a documentation on how to use the feature. Not sure if we can consider it as a "tutorial". It is referenced from the "Developing with Ignition CMake" and place inside the tutorials since I did not find a better place for it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been using the term "tutorial" to refer to all kinds of documentation, guides, instructions, etc. So I wouldn't worry too much about it. It's good to give the document more visibility by placing it on the main page.

In any case, note that we're not publishing tutorials for ign-cmake on the main site yet:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @azeey and @chapulina for the information. I added the entry in c55d115


### Using CCache

Expand Down
41 changes: 41 additions & 0 deletions tutorials/ign_cmake_sanitizers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Sanitizer Builds

## Original source and Copyright

The original work for these instructions is the project https:/StableCoder/cmake-scripts/ licensed under the Apache-2 with the following copyright:

> Copyright (C) 2018-2022 by George Cave - [email protected]

## Description

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](https://clang.llvm.org/docs/LeakSanitizer.html) 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](https://clang.llvm.org/docs/AddressSanitizer.html) 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](https://clang.llvm.org/docs/ThreadSanitizer.html) detects data races for multi-threaded code.
- [UndefinedBehaviourSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) 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](https://clang.llvm.org/docs/MemorySanitizer.html) detects uninitialized reads.
- [Control Flow Integrity](https://clang.llvm.org/docs/ControlFlowIntegrity.html) is designed to detect certain forms of undefined behaviour that can potentially allow attackers to subvert the program's control flow.

These are used by declaring the `IGN_SANITIZER` CMake variable as string containing any of:
- Address
- Memory
- MemoryWithOrigins
- Undefined
- Thread
- Leak
- CFI

Multiple values are allowed, e.g. `-DIGN_SANITIZER=Address,Leak` but some sanitizers cannot be combined together, e.g.`-DIGN_SANITIZER=Address,Memory` will result in configuration error. The delimeter character is not required and `-DIGN_SANITIZER=AddressLeak` would work as well.