#ifndef INTERCEPTION_H
#define INTERCEPTION_H
+#include "sanitizer_common/sanitizer_asm.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#if !SANITIZER_LINUX && !SANITIZER_FREEBSD && !SANITIZER_APPLE && \
// for more details). To intercept such functions you need to use the
// INTERCEPTOR_WITH_SUFFIX(...) macro.
-// How it works:
-// To replace system functions on Linux we just need to declare functions
-// with same names in our library and then obtain the real function pointers
+// How it works on Linux
+// ---------------------
+//
+// To replace system functions on Linux we just need to declare functions with
+// the same names in our library and then obtain the real function pointers
// using dlsym().
-// There is one complication. A user may also intercept some of the functions
-// we intercept. To resolve this we declare our interceptors with __interceptor_
-// prefix, and then make actual interceptors weak aliases to __interceptor_
-// functions.
//
-// This is not so on Mac OS, where the two-level namespace makes
-// our replacement functions invisible to other libraries. This may be overcomed
-// using the DYLD_FORCE_FLAT_NAMESPACE, but some errors loading the shared
-// libraries in Chromium were noticed when doing so.
+// There is one complication: a user may also intercept some of the functions we
+// intercept. To allow for up to 3 interceptors (including ours) of a given
+// function "func", the interceptor implementation is in ___interceptor_func,
+// which is aliased by a weak function __interceptor_func, which in turn is
+// aliased (via a trampoline) by weak wrapper function "func".
+//
+// Most user interceptors should define a foreign interceptor as follows:
+//
+// - provide a non-weak function "func" that performs interception;
+// - if __interceptor_func exists, call it to perform the real functionality;
+// - if it does not exist, figure out the real function and call it instead.
+//
+// In rare cases, a foreign interceptor (of another dynamic analysis runtime)
+// may be defined as follows:
+//
+// - provide a non-weak function __interceptor_func that performs interception;
+// - if ___interceptor_func exists, call it to perform the real functionality;
+// - if it does not exist, figure out the real function and call it instead;
+// - provide a weak function "func" that is an alias to __interceptor_func.
+//
+// With this protocol, sanitizer interceptors, foreign user interceptors, and
+// foreign interceptors of other dynamic analysis runtimes, or any combination
+// thereof, may co-exist simultaneously.
+//
+// How it works on Mac OS
+// ----------------------
+//
+// This is not so on Mac OS, where the two-level namespace makes our replacement
+// functions invisible to other libraries. This may be overcomed using the
+// DYLD_FORCE_FLAT_NAMESPACE, but some errors loading the shared libraries in
+// Chromium were noticed when doing so.
+//
// Instead we create a dylib containing a __DATA,__interpose section that
// associates library functions with their wrappers. When this dylib is
-// preloaded before an executable using DYLD_INSERT_LIBRARIES, it routes all
-// the calls to interposed functions done through stubs to the wrapper
-// functions.
+// preloaded before an executable using DYLD_INSERT_LIBRARIES, it routes all the
+// calls to interposed functions done through stubs to the wrapper functions.
+//
// As it's decided at compile time which functions are to be intercepted on Mac,
// INTERCEPT_FUNCTION() is effectively a no-op on this system.
# define DECLARE_WRAPPER_WINAPI(ret_type, func, ...) \
extern "C" __declspec(dllimport) ret_type __stdcall func(__VA_ARGS__);
#elif !SANITIZER_FUCHSIA // LINUX, FREEBSD, NETBSD, SOLARIS
-# define WRAP(x) __interceptor_ ## x
-# define TRAMPOLINE(x) WRAP(x)
+# define WRAP(x) ___interceptor_ ## x
+# define TRAMPOLINE(x) __interceptor_trampoline_ ## x
# define INTERCEPTOR_ATTRIBUTE __attribute__((visibility("default")))
# if SANITIZER_FREEBSD || SANITIZER_NETBSD
// FreeBSD's dynamic linker (incompliantly) gives non-weak symbols higher
// priority than weak ones so weak aliases won't work for indirect calls
// in position-independent (-fPIC / -fPIE) mode.
-# define OVERRIDE_ATTRIBUTE
+# define __ASM_WEAK_WRAPPER(func)
# else // SANITIZER_FREEBSD || SANITIZER_NETBSD
-# define OVERRIDE_ATTRIBUTE __attribute__((weak))
+# define __ASM_WEAK_WRAPPER(func) ".weak " #func "\n"
# endif // SANITIZER_FREEBSD || SANITIZER_NETBSD
+//
+// Note: Weak aliases of weak aliases do not work, therefore we need to set up a
+// trampoline function. The function "func" is a weak alias to the trampoline
+// (so that we may check if "func" was overridden), which calls the weak
+// function __interceptor_func, which in turn aliases the actual interceptor
+// implementation ___interceptor_func:
+//
+// [wrapper "func": weak] --(alias)--> [TRAMPOLINE(func)]
+// |
+// +--------(tail call)-------+
+// |
+// v
+// [__interceptor_func: weak] --(alias)--> [WRAP(func)]
+//
+// We use inline assembly to define most of this, because not all compilers
+// support functions with the "naked" attribute with every architecture.
+//
# define DECLARE_WRAPPER(ret_type, func, ...) \
- extern "C" ret_type func(__VA_ARGS__) INTERCEPTOR_ATTRIBUTE \
- OVERRIDE_ATTRIBUTE ALIAS(WRAP(func));
+ extern "C" ret_type func(__VA_ARGS__); \
+ extern "C" ret_type TRAMPOLINE(func)(__VA_ARGS__); \
+ extern "C" ret_type __interceptor_##func(__VA_ARGS__) \
+ INTERCEPTOR_ATTRIBUTE __attribute__((weak)) ALIAS(WRAP(func)); \
+ asm( \
+ ".text\n" \
+ __ASM_WEAK_WRAPPER(func) \
+ ".set " #func ", " SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
+ ".globl " SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
+ ".type " SANITIZER_STRINGIFY(TRAMPOLINE(func)) ", @function\n" \
+ SANITIZER_STRINGIFY(TRAMPOLINE(func)) ":\n" \
+ SANITIZER_STRINGIFY(CFI_STARTPROC) "\n" \
+ ASM_INL_PPC64_GLOBALENTRY(SANITIZER_STRINGIFY(TRAMPOLINE(func))) \
+ SANITIZER_STRINGIFY(ASM_TAIL_CALL) " __interceptor_" \
+ SANITIZER_STRINGIFY(ASM_PREEMPTIBLE_SYM(func)) "\n" \
+ SANITIZER_STRINGIFY(CFI_ENDPROC) "\n" \
+ ".size " SANITIZER_STRINGIFY(TRAMPOLINE(func)) ", " \
+ ".-" SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
+ );
#endif
#if SANITIZER_FUCHSIA
set(INTERCEPTION_UNITTESTS
interception_linux_test.cpp
+ interception_linux_foreign_test.cpp
interception_test_main.cpp
interception_win_test.cpp
)
-I${COMPILER_RT_SOURCE_DIR}/lib/interception
-DSANITIZER_COMMON_NO_REDEFINE_BUILTINS
-fno-rtti
+ -fno-builtin-isdigit
+ -fno-builtin-isalpha
+ -fno-builtin-isalnum
+ -fno-builtin-islower
-O2
-Werror=sign-compare)
--- /dev/null
+//===-- interception_linux_foreign_test.cpp -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of ThreadSanitizer/AddressSanitizer runtime.
+//
+// Tests that foreign interceptors work.
+//
+//===----------------------------------------------------------------------===//
+
+// Do not declare functions in ctype.h.
+#define __NO_CTYPE
+
+#include "gtest/gtest.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+#if SANITIZER_LINUX
+
+extern "C" int isalnum(int d);
+extern "C" int __interceptor_isalpha(int d);
+extern "C" int ___interceptor_isalnum(int d); // the sanitizer interceptor
+extern "C" int ___interceptor_islower(int d); // the sanitizer interceptor
+
+namespace __interception {
+extern int isalpha_called;
+extern int isalnum_called;
+extern int islower_called;
+} // namespace __interception
+using namespace __interception;
+
+// Direct foreign interceptor. This is the "normal" protocol that other
+// interceptors should follow.
+extern "C" int isalpha(int d) {
+ // Use non-commutative arithmetic to verify order of calls.
+ isalpha_called = isalpha_called * 10 + 1;
+ return __interceptor_isalpha(d);
+}
+
+// Indirect foreign interceptor. This pattern should only be used to co-exist
+// with direct foreign interceptors and sanitizer interceptors.
+extern "C" int __interceptor_isalnum(int d) {
+ isalnum_called = isalnum_called * 10 + 1;
+ return ___interceptor_isalnum(d);
+}
+
+extern "C" int __interceptor_islower(int d) {
+ islower_called = islower_called * 10 + 2;
+ return ___interceptor_islower(d);
+}
+
+extern "C" int islower(int d) {
+ islower_called = islower_called * 10 + 1;
+ return __interceptor_islower(d);
+}
+
+namespace __interception {
+
+TEST(ForeignInterception, ForeignOverrideDirect) {
+ isalpha_called = 0;
+ EXPECT_NE(0, isalpha('a'));
+ EXPECT_EQ(13, isalpha_called);
+ isalpha_called = 0;
+ EXPECT_EQ(0, isalpha('_'));
+ EXPECT_EQ(13, isalpha_called);
+}
+
+TEST(ForeignInterception, ForeignOverrideIndirect) {
+ isalnum_called = 0;
+ EXPECT_NE(0, isalnum('a'));
+ EXPECT_EQ(13, isalnum_called);
+ isalnum_called = 0;
+ EXPECT_EQ(0, isalnum('_'));
+ EXPECT_EQ(13, isalnum_called);
+}
+
+TEST(ForeignInterception, ForeignOverrideThree) {
+ islower_called = 0;
+ EXPECT_NE(0, islower('a'));
+ EXPECT_EQ(123, islower_called);
+ islower_called = 0;
+ EXPECT_EQ(0, islower('_'));
+ EXPECT_EQ(123, islower_called);
+}
+
+} // namespace __interception
+
+#endif // SANITIZER_LINUX
//
//===----------------------------------------------------------------------===//
-// Do not declare isdigit in ctype.h.
+// Do not declare functions in ctype.h.
#define __NO_CTYPE
#include "interception/interception.h"
+#include <stdlib.h>
+
#include "gtest/gtest.h"
-// Too slow for debug build
-#if !SANITIZER_DEBUG
#if SANITIZER_LINUX
-static int InterceptorFunctionCalled;
+static int isdigit_called;
+namespace __interception {
+int isalpha_called;
+int isalnum_called;
+int islower_called;
+} // namespace __interception
+using namespace __interception;
DECLARE_REAL(int, isdigit, int);
+DECLARE_REAL(int, isalpha, int);
+DECLARE_REAL(int, isalnum, int);
+DECLARE_REAL(int, islower, int);
+
+INTERCEPTOR(void *, malloc, SIZE_T s) { return calloc(1, s); }
+INTERCEPTOR(void, dummy_doesnt_exist__, ) { __builtin_trap(); }
INTERCEPTOR(int, isdigit, int d) {
- ++InterceptorFunctionCalled;
+ ++isdigit_called;
return d >= '0' && d <= '9';
}
+INTERCEPTOR(int, isalpha, int d) {
+ // Use non-commutative arithmetic to verify order of calls.
+ isalpha_called = isalpha_called * 10 + 3;
+ return (d >= 'a' && d <= 'z') || (d >= 'A' && d <= 'Z');
+}
+
+INTERCEPTOR(int, isalnum, int d) {
+ isalnum_called = isalnum_called * 10 + 3;
+ return __interceptor_isalpha(d) || __interceptor_isdigit(d);
+}
+
+INTERCEPTOR(int, islower, int d) {
+ islower_called = islower_called * 10 + 3;
+ return d >= 'a' && d <= 'z';
+}
+
namespace __interception {
TEST(Interception, InterceptFunction) {
uptr malloc_address = 0;
- EXPECT_TRUE(InterceptFunction("malloc", &malloc_address, 0, 0));
+ EXPECT_TRUE(InterceptFunction("malloc", &malloc_address, (uptr)&malloc,
+ (uptr)&__interceptor_trampoline_malloc));
EXPECT_NE(0U, malloc_address);
- EXPECT_FALSE(InterceptFunction("malloc", &malloc_address, 0, 1));
+ EXPECT_FALSE(InterceptFunction("malloc", &malloc_address, (uptr)&calloc,
+ (uptr)&__interceptor_trampoline_malloc));
uptr dummy_address = 0;
- EXPECT_FALSE(InterceptFunction("dummy_doesnt_exist__", &dummy_address, 0, 0));
+ EXPECT_FALSE(InterceptFunction(
+ "dummy_doesnt_exist__", &dummy_address, (uptr)&dummy_doesnt_exist__,
+ (uptr)&__interceptor_trampoline_dummy_doesnt_exist__));
EXPECT_EQ(0U, dummy_address);
}
EXPECT_TRUE(INTERCEPT_FUNCTION(isdigit));
// After interception, the counter should be incremented.
- InterceptorFunctionCalled = 0;
+ isdigit_called = 0;
EXPECT_NE(0, isdigit('1'));
- EXPECT_EQ(1, InterceptorFunctionCalled);
+ EXPECT_EQ(1, isdigit_called);
EXPECT_EQ(0, isdigit('a'));
- EXPECT_EQ(2, InterceptorFunctionCalled);
+ EXPECT_EQ(2, isdigit_called);
// Calling the REAL function should not affect the counter.
- InterceptorFunctionCalled = 0;
+ isdigit_called = 0;
EXPECT_NE(0, REAL(isdigit)('1'));
EXPECT_EQ(0, REAL(isdigit)('a'));
- EXPECT_EQ(0, InterceptorFunctionCalled);
+ EXPECT_EQ(0, isdigit_called);
+}
+
+TEST(Interception, ForeignOverrideDirect) {
+ // Actual interceptor is overridden.
+ EXPECT_FALSE(INTERCEPT_FUNCTION(isalpha));
+
+ isalpha_called = 0;
+ EXPECT_NE(0, isalpha('a'));
+ EXPECT_EQ(13, isalpha_called);
+ isalpha_called = 0;
+ EXPECT_EQ(0, isalpha('_'));
+ EXPECT_EQ(13, isalpha_called);
+
+ isalpha_called = 0;
+ EXPECT_NE(0, REAL(isalpha)('a'));
+ EXPECT_EQ(0, REAL(isalpha)('_'));
+ EXPECT_EQ(0, isalpha_called);
+}
+
+TEST(Interception, ForeignOverrideIndirect) {
+ // Actual interceptor is _not_ overridden.
+ EXPECT_TRUE(INTERCEPT_FUNCTION(isalnum));
+
+ isalnum_called = 0;
+ EXPECT_NE(0, isalnum('a'));
+ EXPECT_EQ(13, isalnum_called);
+ isalnum_called = 0;
+ EXPECT_EQ(0, isalnum('_'));
+ EXPECT_EQ(13, isalnum_called);
+
+ isalnum_called = 0;
+ EXPECT_NE(0, REAL(isalnum)('a'));
+ EXPECT_EQ(0, REAL(isalnum)('_'));
+ EXPECT_EQ(0, isalnum_called);
+}
+
+TEST(Interception, ForeignOverrideThree) {
+ // Actual interceptor is overridden.
+ EXPECT_FALSE(INTERCEPT_FUNCTION(islower));
+
+ islower_called = 0;
+ EXPECT_NE(0, islower('a'));
+ EXPECT_EQ(123, islower_called);
+ islower_called = 0;
+ EXPECT_EQ(0, islower('A'));
+ EXPECT_EQ(123, islower_called);
+
+ islower_called = 0;
+ EXPECT_NE(0, REAL(islower)('a'));
+ EXPECT_EQ(0, REAL(islower)('A'));
+ EXPECT_EQ(0, islower_called);
}
} // namespace __interception
#endif // SANITIZER_LINUX
-#endif // #if !SANITIZER_DEBUG
# define CFI_RESTORE(reg)
#endif
+#if defined(__x86_64__) || defined(__i386__) || defined(__sparc__)
+# define ASM_TAIL_CALL jmp
+#elif defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \
+ defined(__powerpc__) || defined(__loongarch_lp64)
+# define ASM_TAIL_CALL b
+#elif defined(__s390__)
+# define ASM_TAIL_CALL jg
+#elif defined(__riscv)
+# define ASM_TAIL_CALL tail
+#else
+# error ASM_TAIL_CALL: Unimplemented architecture
+#endif
+
+#ifdef __ELF__
+# if defined(__x86_64__) || defined(__i386__) || defined(__riscv)
+# define ASM_PREEMPTIBLE_SYM(sym) sym@plt
+# elif defined(__powerpc64__)
+# define ASM_PREEMPTIBLE_SYM(sym) sym;nop
+# endif
+#endif // __ELF__
+
+#ifndef ASM_PREEMPTIBLE_SYM
+# define ASM_PREEMPTIBLE_SYM(sym) sym
+#endif // ASM_PREEMPTIBLE_SYM
+
+#ifdef __powerpc64__
+# define ASM_INL_PPC64_GLOBALENTRY(func) \
+ "addis 2, 12, .TOC.-" func "@ha\n" \
+ "addi 2, 2, .TOC.-" func "@l\n" \
+ ".localentry " func ",.-" func "\n"
+# define ASM_PPC64_GLOBALENTRY(func) \
+ addis 2, 12, .TOC.-func@ha; \
+ addi 2, 2, .TOC.-func@l; \
+ .localentry func,.-func
+#else
+# define ASM_INL_PPC64_GLOBALENTRY(func)
+# define ASM_PPC64_GLOBALENTRY(func)
+#endif
+
#if !defined(__APPLE__)
# define ASM_HIDDEN(symbol) .hidden symbol
# define ASM_TYPE_FUNCTION(symbol) .type symbol, %function
# define ASM_SIZE(symbol) .size symbol, .-symbol
# define ASM_SYMBOL(symbol) symbol
# define ASM_SYMBOL_INTERCEPTOR(symbol) symbol
-# define ASM_WRAPPER_NAME(symbol) __interceptor_##symbol
-# define ASM_TRAMPOLINE_ALIAS(symbol, name) \
- .weak symbol; \
- .set symbol, ASM_WRAPPER_NAME(name)
-# define ASM_INTERCEPTOR_TRAMPOLINE(name)
+# define ASM_WRAPPER_NAME(symbol) ___interceptor_##symbol
+# define ASM_TRAMPOLINE_ALIAS(symbol, name) \
+ .weak symbol; \
+ .set symbol, __interceptor_trampoline_##name
+# define ASM_INTERCEPTOR_TRAMPOLINE(name) \
+ .weak __interceptor_##name; \
+ .set __interceptor_##name, ASM_WRAPPER_NAME(name); \
+ .globl __interceptor_trampoline_##name; \
+ ASM_TYPE_FUNCTION(__interceptor_trampoline_##name); \
+ __interceptor_trampoline_##name: \
+ CFI_STARTPROC; \
+ ASM_PPC64_GLOBALENTRY(__interceptor_trampoline_##name); \
+ ASM_TAIL_CALL ASM_PREEMPTIBLE_SYM(__interceptor_##name); \
+ CFI_ENDPROC; \
+ ASM_SIZE(__interceptor_trampoline_##name)
#else
# define ASM_HIDDEN(symbol)
# define ASM_TYPE_FUNCTION(symbol)
if (const char *s = try_strip("__asan_wrap_"))
return s;
} else {
+ if (const char *s = try_strip("___interceptor_"))
+ return s;
if (const char *s = try_strip("__interceptor_"))
return s;
}
result.append(func)
continue
# Export interceptors.
- match = re.match("__interceptor_(.*)", func)
+ match = re.match("_?__interceptor_(.*)", func)
if match:
result.append(func)
# We have to avoid exporting the interceptors for versioned library