-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[sanitizer] Print diagnostic if ptrace syscall fails #151406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
StopTheWorld() spins up a thread that calls ptrace before releasing a mutex; the other thread yields until the mutex is unlocked. If seccomp kills the thread that calls ptrace, the other thread will silently hang. The latter thread cannot use waitpid to detect that the other thread has been killed, because the threads share errno. This patch forks the process to test whether ptrace is allowed. If it fails, it prints an informational message (though it does not abort the sanitizer). Fixes llvm#150380 and google/sanitizers#777
@llvm/pr-subscribers-compiler-rt-sanitizer Author: Thurston Dang (thurstond) ChangesStopTheWorld() spins up a thread that calls ptrace before releasing a mutex; the other thread yields until the mutex is unlocked. If seccomp kills the thread that calls ptrace, the other thread will silently hang. The latter thread cannot use waitpid to detect that the other thread has been killed, because the threads share errno. This patch forks the process 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 Full diff: https://github.com/llvm/llvm-project/pull/151406.diff 1 Files Affected:
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp
index 24929b8c4bd7c..ce21a156d28b6 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp
@@ -403,7 +403,48 @@ struct ScopedSetTracerPID {
}
};
+// This detects whether ptrace is blocked (e.g., by seccomp), by forking and
+// then attempting ptrace.
+// This separate check is necessary because StopTheWorld() creates a *thread*,
+// and therefore cannot use waitpid() due to the shared errno.
+static void TestPTrace() {
+ // Only check the first time this is called.
+ static bool checked = false;
+ if (checked)
+ return;
+ checked = true;
+
+ // fork() should be cheap because of copy-on-write. Besides, this is only
+ // called the first time.
+ int pid = internal_fork();
+
+ if (pid < 0) {
+ int rverrno;
+ if (internal_iserror(pid, &rverrno)) {
+ Report("WARNING: TestPTrace() failed to fork (errno %d)\n", rverrno);
+ }
+ _exit(-1);
+ }
+
+ if (pid == 0) {
+ // Child subprocess
+ internal_ptrace(PTRACE_ATTACH, 0, nullptr, nullptr);
+ _exit (0);
+ } else {
+ int wstatus;
+ internal_waitpid(pid, &wstatus, 0);
+
+ if (WIFSIGNALED(wstatus)) {
+ VReport(0, "Warning: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang.\n");
+ VReport(0, "Child exited with signal %d.\n", WTERMSIG(wstatus));
+ // We don't abort the sanitizer - it's still worth letting the sanitizer try.
+ }
+ }
+}
+
void StopTheWorld(StopTheWorldCallback callback, void *argument) {
+ TestPTrace();
+
StopTheWorldScope in_stoptheworld;
// Prepare the arguments for TracerThread.
struct TracerThreadArgument tracer_thread_argument;
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
// This detects whether ptrace is blocked (e.g., by seccomp), by forking and | ||
// then attempting ptrace. | ||
// This separate check is necessary because StopTheWorld() creates a *thread*, | ||
// and therefore cannot use waitpid() due to the shared errno. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
errno
should be thread local on any reasonable platform?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing comment on line 504 states that errno is shared. I think sanitizers run on all manner of unreasonable platforms?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole code is under SANITIZER_LINUX
though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe the real reason is the clone
syscall?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe the real reason is the clone syscall?
*maybe the real treasure was the clones we made along the way
return; | ||
checked = true; | ||
|
||
// fork() should be cheap because of copy-on-write. Besides, this is only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't call fork
"cheap" exactly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworded
// This detects whether ptrace is blocked (e.g., by seccomp), by forking and | ||
// then attempting ptrace. | ||
// This separate check is necessary because StopTheWorld() creates a *thread*, | ||
// and therefore cannot use waitpid() due to the shared errno. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole code is under SANITIZER_LINUX
though
// This separate check is necessary because StopTheWorld() creates a *thread*, | ||
// and therefore cannot use waitpid() due to the shared errno. | ||
// This separate check is necessary because StopTheWorld() creates a child | ||
// process with a shared virtual address space, and therefore cannot use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is true though. Thread locals should be relative to the thread pointer, which should be different. That wasn't a statement on my part, but a question (you can do all sorts of messed up stuff with clone flags)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. please change comment to explain the weird situation where we share both address space and thread pointer, so the TLS is shared.
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/174/builds/22276 Here is the relevant piece of the build log for the reference
|
…ll fails #151406" Use internal__exit instead of _exit Buildbot: https://lab.llvm.org/buildbot/#/builders/174/builds/22276
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/204/builds/17749 Here is the relevant piece of the build log for the reference
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/140/builds/28289 Here is the relevant piece of the build log for the reference
|
Fixes corner case of llvm#151406 internal_fork() on SPARC actually calls __fork(). We can't safely fork, because it's possible seccomp has been configured to disallow fork() but allow clone(). Also adds some comments/TODOs.
Fixes corner case of llvm#151406 internal_fork() on SPARC actually calls __fork(). We can't safely fork, because it's possible seccomp has been configured to disallow fork() but allow clone(). Also adds some comments/TODOs.
Fixes corner case of llvm#151406 internal_fork() on SPARC actually calls __fork(). We can't safely fork, because it's possible seccomp has been configured to disallow fork() but allow clone(). Also adds some comments/TODOs.
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/56/builds/32558 Here is the relevant piece of the build log for the reference
|
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