Index: lib/asan/asan_dll_thunk.cc =================================================================== --- lib/asan/asan_dll_thunk.cc +++ 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 @@ 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 @@ 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 @@ 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 @@ __asan_option_detect_stack_use_after_return = (__asan_should_detect_stack_use_after_return() != 0); - InterceptASanInterface(); + InterceptHooks(); } } @@ -266,8 +284,14 @@ // 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 Index: lib/interception/interception.h =================================================================== --- lib/interception/interception.h +++ lib/interception/interception.h @@ -127,9 +127,9 @@ # 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__); Index: test/asan/TestCases/Windows/dll_intercept_strlen.cc =================================================================== --- /dev/null +++ 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; +} Index: test/asan/TestCases/Windows/dll_malloc_left_oob.cc =================================================================== --- test/asan/TestCases/Windows/dll_malloc_left_oob.cc +++ test/asan/TestCases/Windows/dll_malloc_left_oob.cc @@ -15,8 +15,8 @@ // // 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); Index: test/asan/TestCases/Windows/dll_malloc_uaf.cc =================================================================== --- test/asan/TestCases/Windows/dll_malloc_uaf.cc +++ test/asan/TestCases/Windows/dll_malloc_uaf.cc @@ -17,13 +17,13 @@ // // 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; }