From dfc7fe5c1e0bfabbb1b6b189b3154b107a3ff7ca Mon Sep 17 00:00:00 2001 From: Timur Iskhodzhanov Date: Thu, 22 May 2014 12:03:40 +0000 Subject: [PATCH] [ASan/Win] Use the new function interception approach to handle function wrappers too; wrap strlen() in DLLs Reviewed at http://reviews.llvm.org/D3871 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@209427 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/asan/asan_dll_thunk.cc | 136 ++++++++++++--------- lib/interception/interception.h | 6 +- .../asan/TestCases/Windows/dll_intercept_strlen.cc | 29 +++++ test/asan/TestCases/Windows/dll_malloc_left_oob.cc | 4 +- test/asan/TestCases/Windows/dll_malloc_uaf.cc | 8 +- 5 files changed, 118 insertions(+), 65 deletions(-) create mode 100644 test/asan/TestCases/Windows/dll_intercept_strlen.cc diff --git a/lib/asan/asan_dll_thunk.cc b/lib/asan/asan_dll_thunk.cc index 1487847ed..b7ff6d8aa 100644 --- a/lib/asan/asan_dll_thunk.cc +++ b/lib/asan/asan_dll_thunk.cc @@ -22,7 +22,7 @@ #ifdef ASAN_DLL_THUNK #include "sanitizer_common/sanitizer_interception.h" -// ----------------- Helper functions and macros --------------------- {{{1 +// ---------- Function interception helper functions and macros ----------- {{{1 extern "C" { void *__stdcall GetModuleHandleA(const char *module_name); void *__stdcall GetProcAddress(void *module, const char *proc_name); @@ -36,68 +36,125 @@ static void *getRealProcAddressOrDie(const char *name) { return ret; } +// We need to intercept some functions (e.g. ASan interface, memory allocator -- +// let's call them "hooks") exported by the DLL thunk and forward the hooks to +// the runtime in the main module. +// However, we don't want to keep two lists of these hooks. +// To avoid that, the list of hooks should be defined using the +// INTERCEPT_WHEN_POSSIBLE macro. Then, all these hooks can be intercepted +// at once by calling INTERCEPT_HOOKS(). + +// Use macro+template magic to automatically generate the list of hooks. +// Each hook at line LINE defines a template class with a static +// FunctionInterceptor::Execute() method intercepting the hook. +// The default implementation of FunctionInterceptor is to call +// the Execute() method corresponding to the previous line. +template +struct FunctionInterceptor { + static void Execute() { FunctionInterceptor::Execute(); } +}; + +// There shouldn't be any hooks with negative definition line number. +template<> +struct FunctionInterceptor<0> { + static void Execute() {} +}; + +#define INTERCEPT_WHEN_POSSIBLE(main_function, dll_function) \ + template<> struct FunctionInterceptor<__LINE__> { \ + static void Execute() { \ + void *wrapper = getRealProcAddressOrDie(main_function); \ + if (!__interception::OverrideFunction((uptr)dll_function, \ + (uptr)wrapper, 0)) \ + abort(); \ + FunctionInterceptor<__LINE__-1>::Execute(); \ + } \ + }; + +// Special case of hooks -- ASan own interface functions. Those are only called +// after __asan_init, thus an empty implementation is sufficient. +#define INTERFACE_FUNCTION(name) \ + extern "C" void name() { __debugbreak(); } \ + INTERCEPT_WHEN_POSSIBLE(#name, name) + +// INTERCEPT_HOOKS must be used after the last INTERCEPT_WHEN_POSSIBLE. +#define INTERCEPT_HOOKS FunctionInterceptor<__LINE__>::Execute + +static void InterceptHooks(); +// }}} + +// ---------- Function wrapping helpers ----------------------------------- {{{1 #define WRAP_V_V(name) \ extern "C" void name() { \ typedef void (*fntype)(); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ fn(); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_V_W(name) \ extern "C" void name(void *arg) { \ typedef void (*fntype)(void *arg); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ fn(arg); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_V_WW(name) \ extern "C" void name(void *arg1, void *arg2) { \ typedef void (*fntype)(void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ fn(arg1, arg2); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_V_WWW(name) \ extern "C" void name(void *arg1, void *arg2, void *arg3) { \ typedef void *(*fntype)(void *, void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ fn(arg1, arg2, arg3); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_V(name) \ extern "C" void *name() { \ typedef void *(*fntype)(); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_W(name) \ extern "C" void *name(void *arg) { \ typedef void *(*fntype)(void *arg); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_WW(name) \ extern "C" void *name(void *arg1, void *arg2) { \ typedef void *(*fntype)(void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg1, arg2); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_WWW(name) \ extern "C" void *name(void *arg1, void *arg2, void *arg3) { \ typedef void *(*fntype)(void *, void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg1, arg2, arg3); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_WWWW(name) \ extern "C" void *name(void *arg1, void *arg2, void *arg3, void *arg4) { \ typedef void *(*fntype)(void *, void *, void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg1, arg2, arg3, arg4); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_WWWWW(name) \ extern "C" void *name(void *arg1, void *arg2, void *arg3, void *arg4, \ @@ -105,7 +162,8 @@ static void *getRealProcAddressOrDie(const char *name) { typedef void *(*fntype)(void *, void *, void *, void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg1, arg2, arg3, arg4, arg5); \ - } + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); #define WRAP_W_WWWWWW(name) \ extern "C" void *name(void *arg1, void *arg2, void *arg3, void *arg4, \ @@ -113,48 +171,8 @@ static void *getRealProcAddressOrDie(const char *name) { typedef void *(*fntype)(void *, void *, void *, void *, void *, void *); \ static fntype fn = (fntype)getRealProcAddressOrDie(#name); \ return fn(arg1, arg2, arg3, arg4, arg5, arg6); \ - } -// }}} - -// --------- Interface interception helper functions and macros ----------- {{{1 -// We need to intercept the ASan interface exported by the DLL thunk and forward -// all the functions to the runtime in the main module. -// However, we don't want to keep two lists of interface functions. -// To avoid that, the list of interface functions should be defined using the -// INTERFACE_FUNCTION macro. Then, all the interface can be intercepted at once -// by calling INTERCEPT_ASAN_INTERFACE(). - -// Use macro+template magic to automatically generate the list of interface -// functions. Each interface function at line LINE defines a template class -// with a static InterfaceInteceptor::Execute() method intercepting the -// function. The default implementation of InterfaceInteceptor is to call -// the Execute() method corresponding to the previous line. -template -struct InterfaceInteceptor { - static void Execute() { InterfaceInteceptor::Execute(); } -}; - -// There shouldn't be any interface function with negative line number. -template<> -struct InterfaceInteceptor<0> { - static void Execute() {} -}; - -#define INTERFACE_FUNCTION(name) \ - extern "C" void name() { __debugbreak(); } \ - template<> struct InterfaceInteceptor<__LINE__> { \ - static void Execute() { \ - void *wrapper = getRealProcAddressOrDie(#name); \ - if (!__interception::OverrideFunction((uptr)name, (uptr)wrapper, 0)) \ - abort(); \ - InterfaceInteceptor<__LINE__-1>::Execute(); \ - } \ - }; - -// INTERCEPT_ASAN_INTERFACE must be used after the last INTERFACE_FUNCTION. -#define INTERCEPT_ASAN_INTERFACE InterfaceInteceptor<__LINE__>::Execute - -static void InterceptASanInterface(); + } \ + INTERCEPT_WHEN_POSSIBLE(#name, name); // }}} // ----------------- ASan own interface functions -------------------- @@ -178,7 +196,7 @@ extern "C" { __asan_option_detect_stack_use_after_return = (__asan_should_detect_stack_use_after_return() != 0); - InterceptASanInterface(); + InterceptHooks(); } } @@ -266,8 +284,14 @@ WRAP_W_W(_expand_dbg) // TODO(timurrrr): Do we need to add _Crt* stuff here? (see asan_malloc_win.cc). -void InterceptASanInterface() { - INTERCEPT_ASAN_INTERFACE(); +// strlen is an intrinsic function, so we must specify its exact return and +// parameter types to avoid a compiler error. +extern "C" unsigned strlen(const char *s); +INTERCEPT_WHEN_POSSIBLE(WRAPPER_NAME(strlen), strlen); + +// Must be at the end of the file due to the way INTERCEPT_HOOKS is defined. +void InterceptHooks() { + INTERCEPT_HOOKS(); } #endif // ASAN_DLL_THUNK diff --git a/lib/interception/interception.h b/lib/interception/interception.h index 7b6b6475e..ac982f71d 100644 --- a/lib/interception/interception.h +++ b/lib/interception/interception.h @@ -127,9 +127,9 @@ const interpose_substitution substitution_##func_name[] \ # define WRAPPER_NAME(x) #x # define INTERCEPTOR_ATTRIBUTE # else // Static CRT -# define WRAP(x) wrap_##x -# define WRAPPER_NAME(x) "wrap_"#x -# define INTERCEPTOR_ATTRIBUTE +# define WRAP(x) __asan_wrap_##x +# define WRAPPER_NAME(x) "__asan_wrap_"#x +# define INTERCEPTOR_ATTRIBUTE __declspec(dllexport) # endif # define DECLARE_WRAPPER(ret_type, func, ...) \ extern "C" ret_type func(__VA_ARGS__); diff --git a/test/asan/TestCases/Windows/dll_intercept_strlen.cc b/test/asan/TestCases/Windows/dll_intercept_strlen.cc new file mode 100644 index 000000000..684c82c85 --- /dev/null +++ b/test/asan/TestCases/Windows/dll_intercept_strlen.cc @@ -0,0 +1,29 @@ +// RUN: %clangxx_asan -O0 %p/dll_host.cc -Fe%t +// RUN: %clangxx_asan -LD -O0 %s -Fe%t.dll +// FIXME: 'cat' is needed due to PR19744. +// RUN: not %run %t %t.dll 2>&1 | cat | FileCheck %s + +#include +#include + +extern "C" __declspec(dllexport) +int test_function() { + char str[] = "Hello!"; + if (6 != strlen(str)) + return 1; + printf("Initial test OK\n"); + fflush(0); +// CHECK: Initial test OK + + str[6] = '!'; // Removes '\0' at the end! + int len = strlen(str); +// CHECK: AddressSanitizer: stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// FIXME: Should be READ of size 1, see issue 155. +// CHECK: READ of size {{[0-9]+}} at [[ADDR]] thread T0 +// CHECK-NEXT: {{#0 .*}}strlen +// CHECK-NEXT: {{#1 .* test_function .*}}dll_intercept_strlen.cc:[[@LINE-5]] +// +// CHECK: Address [[ADDR]] is located in stack of thread T0 at offset {{.*}} in frame +// CHECK-NEXT: test_function {{.*}}dll_intercept_strlen.cc: + return len > 42; +} diff --git a/test/asan/TestCases/Windows/dll_malloc_left_oob.cc b/test/asan/TestCases/Windows/dll_malloc_left_oob.cc index b0d653991..cc96dd4f0 100644 --- a/test/asan/TestCases/Windows/dll_malloc_left_oob.cc +++ b/test/asan/TestCases/Windows/dll_malloc_left_oob.cc @@ -15,8 +15,8 @@ int test_function() { // // CHECK: [[ADDR]] is located 1 bytes to the left of 42-byte region // CHECK-LABEL: allocated by thread T0 here: -// CHECK: malloc -// CHECK: test_function {{.*}}dll_malloc_left_oob.cc:[[@LINE-10]] +// CHECK-NEXT: malloc +// CHECK-NEXT: test_function {{.*}}dll_malloc_left_oob.cc:[[@LINE-10]] // CHECK-NEXT: main {{.*}}dll_host.cc // CHECK-LABEL: SUMMARY free(buffer); diff --git a/test/asan/TestCases/Windows/dll_malloc_uaf.cc b/test/asan/TestCases/Windows/dll_malloc_uaf.cc index 9dc3949b6..7a37b44dc 100644 --- a/test/asan/TestCases/Windows/dll_malloc_uaf.cc +++ b/test/asan/TestCases/Windows/dll_malloc_uaf.cc @@ -17,13 +17,13 @@ int test_function() { // // CHECK: [[ADDR]] is located 0 bytes inside of 42-byte region // CHECK-LABEL: freed by thread T0 here: -// CHECK: free -// CHECK: test_function {{.*}}dll_malloc_uaf.cc:[[@LINE-10]] +// CHECK-NEXT: free +// CHECK-NEXT: test_function {{.*}}dll_malloc_uaf.cc:[[@LINE-10]] // CHECK-NEXT: main {{.*}}dll_host // // CHECK-LABEL: previously allocated by thread T0 here: -// CHECK: malloc -// CHECK: test_function {{.*}}dll_malloc_uaf.cc:[[@LINE-16]] +// CHECK-NEXT: malloc +// CHECK-NEXT: test_function {{.*}}dll_malloc_uaf.cc:[[@LINE-16]] // CHECK-NEXT: main {{.*}}dll_host return 0; } -- 2.11.4.GIT