From a9f0cc62f2cc118229e5fdec70640be6b6dc385a Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Sun, 20 Jan 2019 02:11:43 +0100 Subject: [PATCH] Add AddressSanitizer support to fibers. --- .travis.yml | 2 +- runtime/CMakeLists.txt | 6 +++++ runtime/druntime | 2 +- tests/lit.site.cfg.in | 4 +++ tests/sanitizers/asan_fiber.d | 40 +++++++++++++++++++++++++++ tests/sanitizers/asan_fiber_main.d | 43 ++++++++++++++++++++++++++++++ tests/sanitizers/lit.local.cfg | 6 ++++- 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 tests/sanitizers/asan_fiber.d create mode 100644 tests/sanitizers/asan_fiber_main.d diff --git a/.travis.yml b/.travis.yml index e86ad6698da..f2c3f5a3262 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ matrix: # env: LLVM_VERSION=7.0.1 OPTS="-DBUILD_SHARED_LIBS=ON" - os: linux d: ldc-beta - env: LLVM_VERSION=6.0.1 OPTS="-DBUILD_SHARED_LIBS=OFF -DLIB_SUFFIX=64" + env: LLVM_VERSION=6.0.1 OPTS="-DBUILD_SHARED_LIBS=OFF -DLIB_SUFFIX=64 -DRT_SUPPORT_SANITIZERS=ON" - os: linux d: ldc env: LLVM_VERSION=5.0.2 OPTS="-DBUILD_SHARED_LIBS=ON -DLIB_SUFFIX=64" diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 7513ab89f72..6f882924965 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -47,9 +47,15 @@ set(LD_FLAGS "" CACHE STRING "Runt set(C_SYSTEM_LIBS AUTO CACHE STRING "C system libraries for linking shared libraries and test runners, separated by ';'") set(TARGET_SYSTEM AUTO CACHE STRING "Target OS/toolchain for cross-compilation (e.g., 'Linux;UNIX', 'Darwin;APPLE;UNIX', 'Windows;MSVC')") set(LDC_TARGET_PRESET "" CACHE STRING "Use a preset target configuration for cross-compilation, by passing format OS-arch (e.g., 'Linux-arm', 'Mac-x64', 'Windows-aarch64', 'Android-x86')") +set(RT_SUPPORT_SANITIZERS OFF CACHE BOOL "Build runtime libraries with sanitizer support (e.g. for AddressSanitizer)") set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) +if (RT_SUPPORT_SANITIZERS) + message(STATUS "Building runtime libraries with sanitizer support") + set(D_FLAGS "${D_FLAGS};-d-version=SupportSanitizers") +endif() + # Find gnu make, use specific version first (BSD) and fall back to default 'make' (LINUX) find_program(GNU_MAKE_BIN NAMES gmake gnumake make) if ("${GNU_MAKE_BIN}" STREQUAL "") diff --git a/runtime/druntime b/runtime/druntime index dbf2e62d1b5..1971de4e314 160000 --- a/runtime/druntime +++ b/runtime/druntime @@ -1 +1 @@ -Subproject commit dbf2e62d1b5403317b8e740ce489ad4e8be679fb +Subproject commit 1971de4e31420089ebbfd0fd20a4cd4b4a45dd98 diff --git a/tests/lit.site.cfg.in b/tests/lit.site.cfg.in index 90a6f7e33c0..dba55ccd39f 100644 --- a/tests/lit.site.cfg.in +++ b/tests/lit.site.cfg.in @@ -27,6 +27,7 @@ config.gnu_make_bin = "@GNU_MAKE_BIN@" config.ldc_host_arch = "@LDC_HOST_ARCH@" config.ldc_with_lld = "@LDC_WITH_LLD@" == "ON" config.spirv_enabled = @LLVM_SPIRV_FOUND@ +0 # LLVM_SPIRV_FOUND is blank if not set +config.rt_supports_sanitizers = "@RT_SUPPORT_SANITIZERS@" == "ON" config.name = 'LDC' @@ -92,6 +93,9 @@ for t in config.llvm_targetsstr.split(';'): if config.spirv_enabled: config.available_features.add('target_SPIRV') +if config.rt_supports_sanitizers: + config.available_features.add('RTSupportsSanitizers') + # Add specific features for Windows x86/x64 testing if (platform.system() == 'Windows') and (config.default_target_bits == 32): config.available_features.add('Windows_x86') diff --git a/tests/sanitizers/asan_fiber.d b/tests/sanitizers/asan_fiber.d new file mode 100644 index 00000000000..80de141a34a --- /dev/null +++ b/tests/sanitizers/asan_fiber.d @@ -0,0 +1,40 @@ +// AddressSanitizer: Test stack overflow detection of an array on a fiber's local stack. + +// REQUIRES: ASan, RTSupportsSanitizers + +// RUN: %ldc -g -fsanitize=address %s -of=%t%exe && not %t%exe 2>&1 | FileCheck %s +// RUN: %ldc -g -fsanitize=address %s -of=%t%exe -d-version=BAD_AFTER_YIELD && not %t%exe 2>&1 | FileCheck %s + +import core.thread; + +// Note: the ordering of `foo` and `prefoo` is intentional to ease FileCheck checking line numbers, +// because of the order in which ASan reports the stack buffer overflow. + +void foo(int* ptr) +{ + version (BAD_AFTER_YIELD) + Fiber.yield(); + + // CHECK: stack-buffer-overflow + // CHECK: WRITE of size 4 + // CHECK-NEXT: #0 {{.*}} in {{.*foo.*}} {{.*}}asan_fiber.d:[[@LINE+1]] + ptr[10] = 1; + +} + +// CHECK-NOT: wild pointer +// CHECK: Address {{.*}} is located in stack of +// CHECK-NEXT: #0 {{.*}} in {{.*prefoo.*}} {{.*}}asan_fiber.d:[[@LINE+1]] +void prefoo() +{ + int[10] a; + foo(&a[0]); +} + +void main() +{ + auto fib = new Fiber(&prefoo); + fib.call(); + version (BAD_AFTER_YIELD) + fib.call(); +} diff --git a/tests/sanitizers/asan_fiber_main.d b/tests/sanitizers/asan_fiber_main.d new file mode 100644 index 00000000000..387c03648d6 --- /dev/null +++ b/tests/sanitizers/asan_fiber_main.d @@ -0,0 +1,43 @@ +// AddressSanitizer: Test stack overflow detection inside a fiber of an array on main's stack. + +// REQUIRES: ASan, RTSupportsSanitizers + +// RUN: %ldc -g -fsanitize=address %s -of=%t1%exe && not %t1%exe 2>&1 | FileCheck %s +// RUN: %ldc -g -fsanitize=address %s -of=%t22%exe -d-version=BAD_AFTER_YIELD && not %t22%exe 2>&1 | FileCheck %s + +// Test with fake stack enabled +// RUN: env %env_asan_opts=detect_stack_use_after_return=true not %t1%exe 2>&1 | FileCheck %s --check-prefix=FAKESTACK +// RUN: env %env_asan_opts=detect_stack_use_after_return=true not %t22%exe 2>&1 | FileCheck %s --check-prefix=FAKESTACK + +import core.thread; + +void foo(int* arr) +{ + version (BAD_AFTER_YIELD) + Fiber.yield(); + + // CHECK: stack-buffer-overflow + // CHECK: WRITE of size 4 + // CHECK-NEXT: #0 {{.*}} in {{.*foo.*}} {{.*}}asan_fiber_main.d:[[@LINE+1]] + arr[10] = 1; // out-of-bounds write +} + +// Without fake stack, ASan only keeps track of the current stack and thus reports +// the bad memory location as a "wild pointer". +// But with fake stack enabled we get something much better: +// FAKESTACK: Address {{.*}} is located in stack of +// FAKESTACK: #0 {{.*}} in {{.*main.*}} {{.*}}asan_fiber_main.d:[[@LINE+1]] +void main() +{ + int[10] a; + int b; + + // Use an extra variable instead of passing `&a[0]` directly to `foo`. + // This is to keep `a` on the stack: `ptr` may be heap allocated because + // it is used in the lambda (delegate). + int* ptr = &a[0]; + auto fib = new Fiber(() => foo(ptr)); + fib.call(); + version (BAD_AFTER_YIELD) + fib.call(); +} diff --git a/tests/sanitizers/lit.local.cfg b/tests/sanitizers/lit.local.cfg index b316f638226..6ec16ca71a9 100644 --- a/tests/sanitizers/lit.local.cfg +++ b/tests/sanitizers/lit.local.cfg @@ -19,7 +19,11 @@ if 'ASan' in config.available_features: # On Darwin, ASan defaults to `abort_on_error=1`, which would make tests run # much slower. Let's override this and run lit tests with 'abort_on_error=0'. # Also, make sure we do not overwhelm the syslog while testing. - config.environment['ASAN_OPTIONS'] = 'abort_on_error=0:log_to_syslog=0' + default_asan_options = 'abort_on_error=0:log_to_syslog=0' + config.environment['ASAN_OPTIONS'] = default_asan_options + # And add a convenience substitution so we can append to the default ASAN_OPTIONS + config.substitutions.append(('%env_asan_opts=', + 'env ASAN_OPTIONS=' + default_asan_options + ':')) # Note: To get line numbers in stack traces on Darwin, we need to run dsymutil on the binary, # because llvm-symbolizer does not look at the object file for debug info.