Skip to content

Commit a708b4b

Browse files
authored
[sanitizer] Print diagnostic if ptrace syscall fails (#151406)
StopTheWorld() clones a child process (with shared virtual address space and shared TLS) that calls ptrace before releasing a mutex; the parent process yields until the mutex is unlocked. If seccomp kills the child process, the parent process will silently hang. The parent process cannot use waitpid to detect that the child process has been killed, because the processes share errno. This patch forks the process one-time to test whether ptrace is allowed. If it fails, it prints an informational message (though it does not abort the sanitizer). Fixes #150380 and google/sanitizers#777
1 parent 9f7f3d6 commit a708b4b

File tree

1 file changed

+49
-1
lines changed

1 file changed

+49
-1
lines changed

compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,54 @@ struct ScopedSetTracerPID {
403403
}
404404
};
405405

406+
// This detects whether ptrace is blocked (e.g., by seccomp), by forking and
407+
// then attempting ptrace.
408+
// This separate check is necessary because StopTheWorld() creates a child
409+
// process with a shared virtual address space and shared TLS, and therefore
410+
// cannot use waitpid() due to the shared errno.
411+
static void TestPTrace() {
412+
// Heuristic: only check the first time this is called. This is not always
413+
// correct (e.g., user manually triggers leak detection, then updates
414+
// seccomp, then leak detection is triggered again).
415+
static bool checked = false;
416+
if (checked)
417+
return;
418+
checked = true;
419+
420+
// We hope that fork() is not too expensive, because of copy-on-write.
421+
// Besides, this is only called the first time.
422+
int pid = internal_fork();
423+
424+
if (pid < 0) {
425+
int rverrno;
426+
if (internal_iserror(pid, &rverrno)) {
427+
Report("WARNING: TestPTrace() failed to fork (errno %d)\n", rverrno);
428+
}
429+
_exit(-1);
430+
}
431+
432+
if (pid == 0) {
433+
// Child subprocess
434+
internal_ptrace(PTRACE_ATTACH, 0, nullptr, nullptr);
435+
_exit(0);
436+
} else {
437+
int wstatus;
438+
internal_waitpid(pid, &wstatus, 0);
439+
440+
if (WIFSIGNALED(wstatus)) {
441+
VReport(0,
442+
"Warning: ptrace appears to be blocked (is seccomp enabled?). "
443+
"LeakSanitizer may hang.\n");
444+
VReport(0, "Child exited with signal %d.\n", WTERMSIG(wstatus));
445+
// We don't abort the sanitizer - it's still worth letting the sanitizer
446+
// try.
447+
}
448+
}
449+
}
450+
406451
void StopTheWorld(StopTheWorldCallback callback, void *argument) {
452+
TestPTrace();
453+
407454
StopTheWorldScope in_stoptheworld;
408455
// Prepare the arguments for TracerThread.
409456
struct TracerThreadArgument tracer_thread_argument;
@@ -457,7 +504,8 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) {
457504
internal_prctl(PR_SET_PTRACER, tracer_pid, 0, 0, 0);
458505
// Allow the tracer thread to start.
459506
tracer_thread_argument.mutex.Unlock();
460-
// NOTE: errno is shared between this thread and the tracer thread.
507+
// NOTE: errno is shared between this thread and the tracer thread
508+
// (clone was called without CLONE_SETTLS / newtls).
461509
// internal_waitpid() may call syscall() which can access/spoil errno,
462510
// so we can't call it now. Instead we for the tracer thread to finish using
463511
// the spin loop below. Man page for sched_yield() says "In the Linux

0 commit comments

Comments
 (0)