be/src/common/asan_cxa_throw_wrap.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | // Fix for the long-standing ASAN-only BE crashes on the thrift RPC |
19 | | // retry/reopen path (and, before the LOG guards were added, inside glog's |
20 | | // LOG(WARNING)), which always aborted with: |
21 | | // |
22 | | // AddressSanitizer: CHECK failed: asan_thread.cpp:... |
23 | | // "((ptr[0] == kCurrentStackFrameMagic)) != (0)" (0x0, 0x0) |
24 | | // |
25 | | // (Same failure family as google/glog#978 and google/sanitizers#1010.) |
26 | | // |
27 | | // Root cause: libthrift and the other third-party static libs are built |
28 | | // WITHOUT ASAN instrumentation. ASAN cleans up the stack shadow on exception |
29 | | // unwinding via __asan_handle_no_return(), emitted by the compiler before |
30 | | // instrumented throws and provided as weak __cxa_throw/_Unwind_RaiseException |
31 | | // interceptors in the runtime for un-instrumented throws. Under the BE's |
32 | | // fully static link (-static-libstdc++/-static-libgcc plus a static sanitizer |
33 | | // runtime) the linker resolves those symbols to libstdc++.a's/libgcc_eh.a's |
34 | | // STRONG definitions, silently discarding the weak interceptors. So when |
35 | | // un-instrumented code throws (e.g. TSocket raising TTransportException on a |
36 | | // FE restart), the unwound instrumented frames keep their stack-shadow |
37 | | // poison; the retry path immediately re-descends over the same stack range, |
38 | | // innocent locals of thrift/glog/libc land on the stale poison, the first |
39 | | // intercepted write reports a false positive, and ASAN aborts while walking |
40 | | // the already-overwritten frame descriptor -- before it can even print a |
41 | | // normal report. RELEASE/DEBUG builds have no shadow memory, which is why |
42 | | // this never happened outside ASAN. |
43 | | // |
44 | | // ASAN builds therefore link with |
45 | | // -Wl,--wrap=__cxa_throw -Wl,--wrap=__cxa_rethrow |
46 | | // -Wl,--wrap=_Unwind_RaiseException |
47 | | // (see the ASAN branch in be/CMakeLists.txt). --wrap redirects every |
48 | | // reference from every input object -- including the members of the |
49 | | // un-instrumented third-party archives and of libstdc++.a itself -- to the |
50 | | // wrappers below, which restore exactly what the official interceptors do: |
51 | | // wipe the stack shadow above the throw point, then forward to the real |
52 | | // implementation (__real___cxa_throw resolves to libstdc++'s definition). |
53 | | // |
54 | | // _Unwind_RaiseException is wrapped as well because not every raise funnels |
55 | | // through __cxa_throw: std::rethrow_exception (libstdc++'s eh_ptr.cc) and |
56 | | // foreign-language unwinders call it directly. This matches the official |
57 | | // ASAN interceptor set for the unwind entry points. Throws via __cxa_throw |
58 | | // run the shadow wipe twice (once per wrapper); that is idempotent and is |
59 | | // also what happens with the stock interceptors. |
60 | | |
61 | | #if defined(ADDRESS_SANITIZER) && defined(__linux__) |
62 | | |
63 | | extern "C" { |
64 | | |
65 | | void __asan_handle_no_return(); |
66 | | void __real___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*)); |
67 | | void __real___cxa_rethrow(); |
68 | | int __real__Unwind_RaiseException(void* exception_object); |
69 | | |
70 | | void __wrap___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*)); |
71 | | void __wrap___cxa_rethrow(); |
72 | | int __wrap__Unwind_RaiseException(void* exception_object); |
73 | | |
74 | 4.86k | void __wrap___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*)) { |
75 | 4.86k | __asan_handle_no_return(); |
76 | 4.86k | __real___cxa_throw(thrown_exception, tinfo, dest); |
77 | 4.86k | } |
78 | | |
79 | 23 | void __wrap___cxa_rethrow() { |
80 | 23 | __asan_handle_no_return(); |
81 | 23 | __real___cxa_rethrow(); |
82 | 23 | } |
83 | | |
84 | | // Unlike the two above this one RETURNS when no handler is found (the caller |
85 | | // then calls std::terminate), so the return value must be forwarded. |
86 | 4.89k | int __wrap__Unwind_RaiseException(void* exception_object) { |
87 | 4.89k | __asan_handle_no_return(); |
88 | 4.89k | return __real__Unwind_RaiseException(exception_object); |
89 | 4.89k | } |
90 | | |
91 | | } // extern "C" |
92 | | |
93 | | #endif // ADDRESS_SANITIZER && __linux__ |