-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathlibraryPatcher_linux.cpp
More file actions
385 lines (348 loc) · 14.3 KB
/
Copy pathlibraryPatcher_linux.cpp
File metadata and controls
385 lines (348 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#include "libraryPatcher.h"
#ifdef __linux__
#include "counters.h"
#include "profiler.h"
#include "guards.h"
#include <cassert>
#include <dlfcn.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
typedef void* (*func_start_routine)(void*);
SpinLock LibraryPatcher::_lock;
const char* LibraryPatcher::_profiler_name = nullptr;
PatchEntry LibraryPatcher::_patched_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_size = 0;
PatchEntry LibraryPatcher::_sigaction_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_sigaction_size = 0;
void LibraryPatcher::initialize() {
if (_profiler_name == nullptr) {
Dl_info info;
void* caller_address = __builtin_return_address(0); // Get return address of caller
bool ret = dladdr(caller_address, &info);
assert(ret);
_profiler_name = realpath(info.dli_fname, nullptr);
_size = 0;
}
}
class RoutineInfo {
private:
func_start_routine _routine;
void* _args;
public:
RoutineInfo(func_start_routine routine, void* args) :
_routine(routine), _args(args) {
}
func_start_routine routine() const {
return _routine;
}
void* args() const {
return _args;
}
};
// Unregister the current thread from the profiler and release its TLS under a
// single SignalBlocker to close the race window between unregisterThread()
// returning and release() acquiring its internal guard (PROF-14603). Without
// this, a SIGVTALRM delivered in that window could call currentSignalSafe()
// and dereference a now-freed ProfiledThread. Kept noinline so the
// SignalBlocker's sigset_t does not appear in the caller's stack frame on
// musl/aarch64 where the deopt blob may corrupt the wrapper's stack guard.
__attribute__((noinline))
static void unregister_and_release(int tid) {
SignalBlocker blocker;
Profiler::unregisterThread(tid);
ProfiledThread::release();
}
#ifdef __aarch64__
// Delete RoutineInfo with profiling signals blocked to prevent ASAN
// allocator lock reentrancy. Kept noinline so SignalBlocker's sigset_t
// does not trigger stack-protector canary in the caller on aarch64.
__attribute__((noinline))
static void delete_routine_info(RoutineInfo* thr) {
SignalBlocker blocker;
delete thr;
}
// Initialize the current thread's TLS, open the init window (PROF-13072), and
// register the thread with the profiler — all under a single SignalBlocker so
// profiling signals cannot fire in the gap between initCurrentThread() and
// startInitWindow(). Kept noinline for the same stack-protector reason as
// delete_routine_info: SignalBlocker's sigset_t must not appear in
// start_routine_wrapper_spec's own stack frame on musl/aarch64.
__attribute__((noinline))
static void init_tls_and_register() {
SignalBlocker blocker;
ProfiledThread::initCurrentThread();
if (ProfiledThread *pt = ProfiledThread::currentSignalSafe()) {
pt->startInitWindow();
}
Profiler::registerThread(ProfiledThread::currentTid());
}
// pthread_cleanup_push callback for start_routine_wrapper_spec.
// Fires when the wrapped routine calls pthread_exit() or the thread is
// canceled. Kept noinline so its stack frame (which may hold a SignalBlocker
// via unregister_and_release) lives outside the DEOPT-corruption zone of
// start_routine_wrapper_spec.
__attribute__((noinline))
static void cleanup_unregister(void*) {
unregister_and_release(ProfiledThread::currentTid());
}
// pthread_cleanup_push declares `struct __ptcb` in the caller's frame. If that
// frame is start_routine_wrapper_spec, the structure sits inside the ~224-byte
// DEOPT-corruption zone and pthread_cleanup_pop(1) would invoke a clobbered
// function pointer. This noinline + no_stack_protector helper hoists the
// cleanup-handler frame out of the corruption zone — its own frame lives
// safely above start_routine_wrapper_spec's.
__attribute__((noinline, no_stack_protector))
static void run_with_musl_cleanup(func_start_routine routine, void* params) {
pthread_cleanup_push(cleanup_unregister, nullptr);
routine(params);
pthread_cleanup_pop(1);
}
// Wrapper around the real start routine.
// The wrapper:
// 1. Register the newly created thread to profiler
// 2. Call real start routine
// 3. Unregister the thread from profiler once the routine is completed.
// This version works around stack corruption observed on musl/aarch64/JDK11:
//
// Empirical observation (hs_err analysis): after DEOPT PACKING fires on a
// thread running compiled lambda$measureContention$0 at sp=0x...49d0, this
// wrapper's frame (sp=0x...5020, ~144 bytes below thread stack top) shows a
// corrupted FP (odd address 0x...5001) and a corrupted stack canary. The
// corruption is confined to the top ~224 bytes of the stack (the region between
// DEOPT PACKING sp and the thread stack top).
//
// The source of the corruption is the interpreter-frame rebuild sequence in
// HotSpot's deoptimization blob (generate_deopt_blob in
// sharedRuntime_aarch64.cpp, openjdk/jdk11u). After popping the compiled
// frame the blob executes "sub sp, sp, caller_adjustment" followed by a loop
// of enter() calls (each doing "stp rfp, lr, [sp, #-16]!") to lay down
// replacement interpreter frames. When musl's small thread stack places this
// wrapper immediately above the compiled frame, the enter() writes can reach
// into this wrapper's frame, corrupting the saved FP and stack canary.
// The mechanism is the same "precarious stack guard corruption" the noinline
// helpers above already defend against for SignalBlocker's sigset_t.
//
// Two symptoms arise from this corruption:
//
// (a) Stack-canary crash: -fstack-protector-strong inserts a canary whenever
// the frame has a non-trivially destructed local (e.g. a Cleanup struct).
// That canary lands in the corruption zone; the epilogue fires
// __stack_chk_fail. no_stack_protector removes the canary.
//
// (b) Corrupted-LR crash: even without a canary, `return` loads the saved LR
// from the corrupted frame and jumps to a garbage address. pthread_exit()
// terminates the thread without using LR. HotSpot on musl returns normally
// from java_start (no forced-unwind), so no exception-based cleanup path
// is needed.
//
// Cleanup reads tid from TLS (via ProfiledThread::currentTid()) rather than
// from a stack variable, so it is correct even after the frame is corrupted.
// pthread_cleanup_push/pop ensures unregister_and_release() also runs when the
// wrapped routine calls pthread_exit() or the thread is canceled.
__attribute__((visibility("hidden"), no_stack_protector))
static void* start_routine_wrapper_spec(void* args) {
RoutineInfo* thr = (RoutineInfo*)args;
func_start_routine routine = thr->routine();
void* params = thr->args();
delete_routine_info(thr);
init_tls_and_register();
// cleanup_unregister fires on pthread_exit() or cancellation from within
// routine(params). The push/pop pair lives inside run_with_musl_cleanup so
// that `struct __ptcb` does not land in this frame's DEOPT-corruption zone.
run_with_musl_cleanup(routine, params);
// pthread_exit instead of 'return': the saved LR in this frame is corrupted
// by DEOPT PACKING; returning would jump to a garbage address.
pthread_exit(nullptr);
__builtin_unreachable();
}
static int pthread_create_hook_spec(pthread_t* thread,
const pthread_attr_t* attr,
func_start_routine start_routine,
void* args) {
RoutineInfo* thr;
{
SignalBlocker blocker;
thr = new RoutineInfo(start_routine, args);
}
int ret = pthread_create(thread, attr, start_routine_wrapper_spec, (void*)thr);
if (ret != 0) {
SignalBlocker blocker;
delete thr;
}
return ret;
}
#endif // __aarch64__
// Wrapper around the real start routine.
// See comments for start_routine_wrapper_spec() for details
__attribute__((visibility("hidden")))
static void* start_routine_wrapper(void* args) {
RoutineInfo* thr = (RoutineInfo*)args;
func_start_routine routine;
void* params;
{
// Block profiling signals while accessing and freeing RoutineInfo
// and during TLS initialization. Under ASAN, new/delete/
// pthread_setspecific are intercepted and acquire ASAN's internal
// allocator lock. A profiling signal during any of these calls
// runs ASAN-instrumented code that tries to acquire the same
// lock, causing deadlock.
// registerThread is also kept inside the blocker so that the CPU
// timer is armed while SIGPROF/SIGVTALRM are masked. Any pending
// signal fires only after signals are re-enabled (when the blocker
// scope exits), at which point JVMThread::current() is still null
// and the guard in CTimer::signalHandler discards the sample safely.
SignalBlocker blocker;
routine = thr->routine();
params = thr->args();
delete thr;
ProfiledThread::initCurrentThread();
ProfiledThread::currentSignalSafe()->startInitWindow();
Profiler::registerThread(ProfiledThread::currentTid());
}
// RAII cleanup: reads tid from TLS in the destructor (same rationale as
// start_routine_wrapper_spec: avoids storing state on a potentially corruptible frame).
// unregister_and_release() wraps the two calls under SignalBlocker (PROF-14603).
struct Cleanup {
~Cleanup() { unregister_and_release(ProfiledThread::currentTid()); }
} cleanup;
routine(params);
return nullptr;
}
static int pthread_create_hook(pthread_t* thread,
const pthread_attr_t* attr,
func_start_routine start_routine,
void* args) {
RoutineInfo* thr;
{
SignalBlocker blocker;
thr = new RoutineInfo(start_routine, args);
}
int ret = pthread_create(thread, attr, start_routine_wrapper, (void*)thr);
if (ret != 0) {
SignalBlocker blocker;
delete thr;
}
return ret;
}
void LibraryPatcher::patch_libraries() {
// LibraryPatcher has yet initialized, only happens in Gtest
if (_profiler_name == nullptr) {
return;
}
TEST_LOG("Patching libraries");
patch_pthread_create();
TEST_LOG("%d libraries patched", _size);
}
void LibraryPatcher::patch_library_unlocked(CodeCache* lib) {
if (lib->name() == nullptr) return;
char path[PATH_MAX];
char* resolved_path = realpath(lib->name(), path);
if (resolved_path != nullptr && // filter out virtual file, e.g. [vdso], etc.
strcmp(resolved_path, _profiler_name) == 0) { // Don't patch self
return;
}
// Don't patch sanitizer runtime libraries — intercepting their internal
// pthread_create calls causes reentrancy and heap corruption under ASAN.
const char* base = strrchr(lib->name(), '/');
base = (base != nullptr) ? base + 1 : lib->name();
if (strncmp(base, "libasan", 7) == 0 ||
strncmp(base, "libtsan", 7) == 0 ||
strncmp(base, "libubsan", 8) == 0) {
return;
}
void** pthread_create_location = (void**)lib->findImport(im_pthread_create);
if (pthread_create_location == nullptr) {
return;
}
for (int index = 0; index < _size; index++) {
// Already patched
if (_patched_entries[index]._lib == lib) {
return;
}
}
TEST_LOG("Patching: %s", lib->name());
void* func = (void*)pthread_create_hook;
#ifdef __aarch64__
// Workaround stack guard corruption in Linux/aarch64/musl/jdk11
if (VM::isHotspot() && OS::isMusl() && VM::java_version() == 11) {
func = (void*)pthread_create_hook_spec;
}
#endif
_patched_entries[_size]._lib = lib;
_patched_entries[_size]._location = pthread_create_location;
_patched_entries[_size]._func = (void*)__atomic_load_n(pthread_create_location, __ATOMIC_RELAXED);
__atomic_store_n(pthread_create_location, func, __ATOMIC_RELAXED);
_size++;
}
void LibraryPatcher::unpatch_libraries() {
TEST_LOG("Restore libraries");
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < _size; index++) {
__atomic_store_n(_patched_entries[index]._location, _patched_entries[index]._func, __ATOMIC_RELAXED);
}
_size = 0;
}
void LibraryPatcher::patch_pthread_create() {
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
int num_of_libs = native_libs.count();
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < num_of_libs; index++) {
CodeCache* lib = native_libs.at(index);
if (lib != nullptr) {
patch_library_unlocked(lib);
}
}
}
// Patch sigaction in all libraries to prevent any library from overwriting
// our SIGSEGV/SIGBUS handlers. This protects against misbehaving libraries
// (like wasmtime) that install broken signal handlers calling malloc().
void LibraryPatcher::patch_sigaction_in_library(CodeCache* lib) {
if (lib->name() == nullptr) return;
if (_profiler_name == nullptr) return; // Not initialized yet
// Don't patch ourselves
char path[PATH_MAX];
char* resolved_path = realpath(lib->name(), path);
if (resolved_path != nullptr && strcmp(resolved_path, _profiler_name) == 0) {
return;
}
// Note: We intentionally patch sanitizer libraries (libasan, libtsan, libubsan) here.
// This keeps our handler on top for recoverable SIGSEGVs (e.g., safefetch) while
// still chaining to the sanitizer's handler for unexpected crashes.
void** sigaction_location = (void**)lib->findImport(im_sigaction);
if (sigaction_location == nullptr) {
return;
}
// Check if already patched or array is full
if (_sigaction_size >= MAX_NATIVE_LIBS) {
return;
}
for (int index = 0; index < _sigaction_size; index++) {
if (_sigaction_entries[index]._lib == lib) {
return;
}
}
void* hook = OS::getSigactionHook();
_sigaction_entries[_sigaction_size]._lib = lib;
_sigaction_entries[_sigaction_size]._location = sigaction_location;
_sigaction_entries[_sigaction_size]._func = (void*)__atomic_load_n(sigaction_location, __ATOMIC_RELAXED);
__atomic_store_n(sigaction_location, hook, __ATOMIC_RELAXED);
_sigaction_size++;
Counters::increment(SIGACTION_PATCHED_LIBS);
}
void LibraryPatcher::patch_sigaction() {
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
int num_of_libs = native_libs.count();
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < num_of_libs; index++) {
CodeCache* lib = native_libs.at(index);
if (lib != nullptr) {
patch_sigaction_in_library(lib);
}
}
}
#endif // __linux__