Skip to content

Commit 37445e9

Browse files
committed
[compiler-rt] Allow 3 simultaneous interceptors on Linux
Rework Linux (and *BSD) interceptors to allow for up to 3 (2 for *BSD) simultaneous interceptors. See code comments for details. The main motivation is to support new sampling sanitizers (in the spirit of GWP-ASan), that have to intercept few functions. Unfortunately, the reality is that there are user interceptors that exist in the wild. To support foreign user interceptors, foreign dynamic analysis interceptors, and compiler-rt interceptors all at the same time, including any combination of them, this change enables up to 3 interceptors on Linux (2 on *BSD). v2: * Revert to to the simpler "weak wrapper -(alias)-> __interceptor" scheme on architectures that cannot implement a trampoline efficiently due to complexities of resolving a preemptible symbol (PowerPC64 ELFv2 global entry, and i386 PIC). * Avoid duplicate intercepted functions in gen_dynamic_list.py, due to matching __interceptor_X and ___interceptor_X. * Fix s390 __tls_get_offset. Reviewed By: dvyukov, MaskRay, vitalybuka Differential Revision: https://reviews.llvm.org/D151085
1 parent 8b0ea48 commit 37445e9

File tree

9 files changed

+368
-61
lines changed

9 files changed

+368
-61
lines changed

compiler-rt/lib/interception/interception.h

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#ifndef INTERCEPTION_H
1515
#define INTERCEPTION_H
1616

17+
#include "sanitizer_common/sanitizer_asm.h"
1718
#include "sanitizer_common/sanitizer_internal_defs.h"
1819

1920
#if !SANITIZER_LINUX && !SANITIZER_FREEBSD && !SANITIZER_APPLE && \
@@ -67,24 +68,50 @@ typedef __sanitizer::OFF64_T OFF64_T;
6768
// for more details). To intercept such functions you need to use the
6869
// INTERCEPTOR_WITH_SUFFIX(...) macro.
6970

70-
// How it works:
71-
// To replace system functions on Linux we just need to declare functions
72-
// with same names in our library and then obtain the real function pointers
71+
// How it works on Linux
72+
// ---------------------
73+
//
74+
// To replace system functions on Linux we just need to declare functions with
75+
// the same names in our library and then obtain the real function pointers
7376
// using dlsym().
74-
// There is one complication. A user may also intercept some of the functions
75-
// we intercept. To resolve this we declare our interceptors with __interceptor_
76-
// prefix, and then make actual interceptors weak aliases to __interceptor_
77-
// functions.
7877
//
79-
// This is not so on Mac OS, where the two-level namespace makes
80-
// our replacement functions invisible to other libraries. This may be overcomed
81-
// using the DYLD_FORCE_FLAT_NAMESPACE, but some errors loading the shared
82-
// libraries in Chromium were noticed when doing so.
78+
// There is one complication: a user may also intercept some of the functions we
79+
// intercept. To allow for up to 3 interceptors (including ours) of a given
80+
// function "func", the interceptor implementation is in ___interceptor_func,
81+
// which is aliased by a weak function __interceptor_func, which in turn is
82+
// aliased (via a trampoline) by weak wrapper function "func".
83+
//
84+
// Most user interceptors should define a foreign interceptor as follows:
85+
//
86+
// - provide a non-weak function "func" that performs interception;
87+
// - if __interceptor_func exists, call it to perform the real functionality;
88+
// - if it does not exist, figure out the real function and call it instead.
89+
//
90+
// In rare cases, a foreign interceptor (of another dynamic analysis runtime)
91+
// may be defined as follows (on supported architectures):
92+
//
93+
// - provide a non-weak function __interceptor_func that performs interception;
94+
// - if ___interceptor_func exists, call it to perform the real functionality;
95+
// - if it does not exist, figure out the real function and call it instead;
96+
// - provide a weak function "func" that is an alias to __interceptor_func.
97+
//
98+
// With this protocol, sanitizer interceptors, foreign user interceptors, and
99+
// foreign interceptors of other dynamic analysis runtimes, or any combination
100+
// thereof, may co-exist simultaneously.
101+
//
102+
// How it works on Mac OS
103+
// ----------------------
104+
//
105+
// This is not so on Mac OS, where the two-level namespace makes our replacement
106+
// functions invisible to other libraries. This may be overcomed using the
107+
// DYLD_FORCE_FLAT_NAMESPACE, but some errors loading the shared libraries in
108+
// Chromium were noticed when doing so.
109+
//
83110
// Instead we create a dylib containing a __DATA,__interpose section that
84111
// associates library functions with their wrappers. When this dylib is
85-
// preloaded before an executable using DYLD_INSERT_LIBRARIES, it routes all
86-
// the calls to interposed functions done through stubs to the wrapper
87-
// functions.
112+
// preloaded before an executable using DYLD_INSERT_LIBRARIES, it routes all the
113+
// calls to interposed functions done through stubs to the wrapper functions.
114+
//
88115
// As it's decided at compile time which functions are to be intercepted on Mac,
89116
// INTERCEPT_FUNCTION() is effectively a no-op on this system.
90117

@@ -131,20 +158,71 @@ const interpose_substitution substitution_##func_name[] \
131158
# define DECLARE_WRAPPER_WINAPI(ret_type, func, ...) \
132159
extern "C" __declspec(dllimport) ret_type __stdcall func(__VA_ARGS__);
133160
#elif !SANITIZER_FUCHSIA // LINUX, FREEBSD, NETBSD, SOLARIS
134-
# define WRAP(x) __interceptor_ ## x
135-
# define TRAMPOLINE(x) WRAP(x)
136161
# define INTERCEPTOR_ATTRIBUTE __attribute__((visibility("default")))
137-
# if SANITIZER_FREEBSD || SANITIZER_NETBSD
162+
# if ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
163+
// Weak aliases of weak aliases do not work, therefore we need to set up a
164+
// trampoline function. The function "func" is a weak alias to the trampoline
165+
// (so that we may check if "func" was overridden), which calls the weak
166+
// function __interceptor_func, which in turn aliases the actual interceptor
167+
// implementation ___interceptor_func:
168+
//
169+
// [wrapper "func": weak] --(alias)--> [TRAMPOLINE(func)]
170+
// |
171+
// +--------(tail call)-------+
172+
// |
173+
// v
174+
// [__interceptor_func: weak] --(alias)--> [WRAP(func)]
175+
//
176+
// We use inline assembly to define most of this, because not all compilers
177+
// support functions with the "naked" attribute with every architecture.
178+
# define WRAP(x) ___interceptor_ ## x
179+
# define TRAMPOLINE(x) __interceptor_trampoline_ ## x
180+
# if SANITIZER_FREEBSD || SANITIZER_NETBSD
138181
// FreeBSD's dynamic linker (incompliantly) gives non-weak symbols higher
139182
// priority than weak ones so weak aliases won't work for indirect calls
140183
// in position-independent (-fPIC / -fPIE) mode.
141-
# define OVERRIDE_ATTRIBUTE
142-
# else // SANITIZER_FREEBSD || SANITIZER_NETBSD
143-
# define OVERRIDE_ATTRIBUTE __attribute__((weak))
144-
# endif // SANITIZER_FREEBSD || SANITIZER_NETBSD
145-
# define DECLARE_WRAPPER(ret_type, func, ...) \
146-
extern "C" ret_type func(__VA_ARGS__) INTERCEPTOR_ATTRIBUTE \
147-
OVERRIDE_ATTRIBUTE ALIAS(WRAP(func));
184+
# define __ASM_WEAK_WRAPPER(func)
185+
# else
186+
# define __ASM_WEAK_WRAPPER(func) ".weak " #func "\n"
187+
# endif // SANITIZER_FREEBSD || SANITIZER_NETBSD
188+
// Keep trampoline implementation in sync with sanitizer_common/sanitizer_asm.h
189+
# define DECLARE_WRAPPER(ret_type, func, ...) \
190+
extern "C" ret_type func(__VA_ARGS__); \
191+
extern "C" ret_type TRAMPOLINE(func)(__VA_ARGS__); \
192+
extern "C" ret_type __interceptor_##func(__VA_ARGS__) \
193+
INTERCEPTOR_ATTRIBUTE __attribute__((weak)) ALIAS(WRAP(func)); \
194+
asm( \
195+
".text\n" \
196+
__ASM_WEAK_WRAPPER(func) \
197+
".set " #func ", " SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
198+
".globl " SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
199+
".type " SANITIZER_STRINGIFY(TRAMPOLINE(func)) ", @function\n" \
200+
SANITIZER_STRINGIFY(TRAMPOLINE(func)) ":\n" \
201+
SANITIZER_STRINGIFY(CFI_STARTPROC) "\n" \
202+
SANITIZER_STRINGIFY(ASM_TAIL_CALL) " __interceptor_" \
203+
SANITIZER_STRINGIFY(ASM_PREEMPTIBLE_SYM(func)) "\n" \
204+
SANITIZER_STRINGIFY(CFI_ENDPROC) "\n" \
205+
".size " SANITIZER_STRINGIFY(TRAMPOLINE(func)) ", " \
206+
".-" SANITIZER_STRINGIFY(TRAMPOLINE(func)) "\n" \
207+
);
208+
# else // ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
209+
// Some architectures cannot implement efficient interceptor trampolines with
210+
// just a plain jump due to complexities of resolving a preemptible symbol. In
211+
// those cases, revert to just this scheme:
212+
//
213+
// [wrapper "func": weak] --(alias)--> [WRAP(func)]
214+
//
215+
# define WRAP(x) __interceptor_ ## x
216+
# define TRAMPOLINE(x) WRAP(x)
217+
# if SANITIZER_FREEBSD || SANITIZER_NETBSD
218+
# define __ATTRIBUTE_WEAK_WRAPPER
219+
# else
220+
# define __ATTRIBUTE_WEAK_WRAPPER __attribute__((weak))
221+
# endif // SANITIZER_FREEBSD || SANITIZER_NETBSD
222+
# define DECLARE_WRAPPER(ret_type, func, ...) \
223+
extern "C" ret_type func(__VA_ARGS__) \
224+
INTERCEPTOR_ATTRIBUTE __ATTRIBUTE_WEAK_WRAPPER ALIAS(WRAP(func));
225+
# endif // ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
148226
#endif
149227

150228
#if SANITIZER_FUCHSIA

compiler-rt/lib/interception/tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ filter_available_targets(INTERCEPTION_UNITTEST_SUPPORTED_ARCH x86_64 i386 mips64
44

55
set(INTERCEPTION_UNITTESTS
66
interception_linux_test.cpp
7+
interception_linux_foreign_test.cpp
78
interception_test_main.cpp
89
interception_win_test.cpp
910
)
@@ -19,6 +20,10 @@ set(INTERCEPTION_TEST_CFLAGS_COMMON
1920
-I${COMPILER_RT_SOURCE_DIR}/lib/interception
2021
-DSANITIZER_COMMON_NO_REDEFINE_BUILTINS
2122
-fno-rtti
23+
-fno-builtin-isdigit
24+
-fno-builtin-isalpha
25+
-fno-builtin-isalnum
26+
-fno-builtin-islower
2227
-O2
2328
-Werror=sign-compare)
2429

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//===-- interception_linux_foreign_test.cpp -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file is a part of ThreadSanitizer/AddressSanitizer runtime.
10+
//
11+
// Tests that foreign interceptors work.
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// Do not declare functions in ctype.h.
16+
#define __NO_CTYPE
17+
18+
#include "gtest/gtest.h"
19+
#include "sanitizer_common/sanitizer_asm.h"
20+
#include "sanitizer_common/sanitizer_internal_defs.h"
21+
22+
#if SANITIZER_LINUX
23+
24+
extern "C" int isalnum(int d);
25+
extern "C" int __interceptor_isalpha(int d);
26+
extern "C" int ___interceptor_isalnum(int d); // the sanitizer interceptor
27+
extern "C" int ___interceptor_islower(int d); // the sanitizer interceptor
28+
29+
namespace __interception {
30+
extern int isalpha_called;
31+
extern int isalnum_called;
32+
extern int islower_called;
33+
} // namespace __interception
34+
using namespace __interception;
35+
36+
// Direct foreign interceptor. This is the "normal" protocol that other
37+
// interceptors should follow.
38+
extern "C" int isalpha(int d) {
39+
// Use non-commutative arithmetic to verify order of calls.
40+
isalpha_called = isalpha_called * 10 + 1;
41+
return __interceptor_isalpha(d);
42+
}
43+
44+
#if ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
45+
// Indirect foreign interceptor. This pattern should only be used to co-exist
46+
// with direct foreign interceptors and sanitizer interceptors.
47+
extern "C" int __interceptor_isalnum(int d) {
48+
isalnum_called = isalnum_called * 10 + 1;
49+
return ___interceptor_isalnum(d);
50+
}
51+
52+
extern "C" int __interceptor_islower(int d) {
53+
islower_called = islower_called * 10 + 2;
54+
return ___interceptor_islower(d);
55+
}
56+
57+
extern "C" int islower(int d) {
58+
islower_called = islower_called * 10 + 1;
59+
return __interceptor_islower(d);
60+
}
61+
#endif // ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
62+
63+
namespace __interception {
64+
65+
TEST(ForeignInterception, ForeignOverrideDirect) {
66+
isalpha_called = 0;
67+
EXPECT_NE(0, isalpha('a'));
68+
EXPECT_EQ(13, isalpha_called);
69+
isalpha_called = 0;
70+
EXPECT_EQ(0, isalpha('_'));
71+
EXPECT_EQ(13, isalpha_called);
72+
}
73+
74+
#if ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
75+
TEST(ForeignInterception, ForeignOverrideIndirect) {
76+
isalnum_called = 0;
77+
EXPECT_NE(0, isalnum('a'));
78+
EXPECT_EQ(13, isalnum_called);
79+
isalnum_called = 0;
80+
EXPECT_EQ(0, isalnum('_'));
81+
EXPECT_EQ(13, isalnum_called);
82+
}
83+
84+
TEST(ForeignInterception, ForeignOverrideThree) {
85+
islower_called = 0;
86+
EXPECT_NE(0, islower('a'));
87+
EXPECT_EQ(123, islower_called);
88+
islower_called = 0;
89+
EXPECT_EQ(0, islower('_'));
90+
EXPECT_EQ(123, islower_called);
91+
}
92+
#endif // ASM_INTERCEPTOR_TRAMPOLINE_SUPPORT
93+
94+
} // namespace __interception
95+
96+
#endif // SANITIZER_LINUX

0 commit comments

Comments
 (0)