Override SIGSYS during profiling signal handler
This is a best-effort mitigation for potential crashes when the
profiling signal handler is triggered within certain secomp'd processes.
In particular, we're working around cases where the seccomp policy
doesn't allow some of the handler syscalls, and has a crashing
disposition towards violations via SECCOMP_RET_TRAP, plus a crashing
SIGSYS handler. While not general, this covers the configurations seen
in practice on Android (which are all using minijail in the same way).
By overriding the SIGSYS handling for the duration of the profiling
handler, we can instead receive such SIGSYS signals, and instead recover
from them in a non-crashing manner (the handler is responsible for
filling the syscall return register, since the syscall itself was
skipped).
For simplicity, we're swallowing all SIGSYS signals during this window,
without trying to figure out whether they're something that could be
caused by the profiling signal handler. I've quite convinced myself that
-ENOSYS seems to be safe to return to all of bionic's syscall wrappers
across the four architectures (looking at gensyscalls + the
special-cases like vfork and clone). It is theoretically possible for
all kinds of conflicting (ab)uses of SIGSYS to exist, but I'm assuming
it's not a realistic concern until proven otherwise.
Tested: manually sigqueue'd configstore on crosshatch, confirmed that
the SIGSYS override log was printed, and the process did not
crash (as it does on master).
Bug: 149328505
Merged-In: Iab8f09e51169807c9d3e1e0bcfd042f09f7df6a4
Change-Id: Iab8f09e51169807c9d3e1e0bcfd042f09f7df6a4
(cherry picked from commit 1dc4122a1d1525ba0892c7c727df8a29942129ce)
diff --git a/libc/bionic/android_profiling_dynamic.cpp b/libc/bionic/android_profiling_dynamic.cpp
index 183a614..54f896c 100644
--- a/libc/bionic/android_profiling_dynamic.cpp
+++ b/libc/bionic/android_profiling_dynamic.cpp
@@ -36,6 +36,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/ucontext.h>
#include <sys/un.h>
#include <async_safe/log.h>
@@ -64,10 +65,10 @@
sigaction(BIONIC_SIGNAL_PROFILER, &action, nullptr);
}
+static void HandleSigsysSeccompOverride(int, siginfo_t*, void*);
static void HandleTracedPerfSignal();
static void HandleProfilingSignal(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) {
- // Avoid clobbering errno.
ErrnoRestorer errno_restorer;
if (info->si_code != SI_QUEUE) {
@@ -86,6 +87,21 @@
return;
}
+ // Temporarily override SIGSYS handling, in a best-effort attempt at not
+ // crashing if we happen to be running in a process with a seccomp filter that
+ // disallows some of the syscalls done by this signal handler. This protects
+ // against SECCOMP_RET_TRAP with a crashing SIGSYS handler (typical of android
+ // minijails). Won't help if the filter is using SECCOMP_RET_KILL_*.
+ // Note: the override is process-wide, but short-lived. The syscalls are still
+ // blocked, but the overridden handler recovers from SIGSYS, and fakes the
+ // syscall return value as ENOSYS.
+ struct sigaction sigsys_override = {};
+ sigsys_override.sa_sigaction = &HandleSigsysSeccompOverride;
+ sigsys_override.sa_flags = SA_SIGINFO;
+
+ struct sigaction old_act = {};
+ sigaction(SIGSYS, &sigsys_override, &old_act);
+
if (signal_value == kHeapprofdSignalValue) {
HandleHeapprofdSignal();
} else if (signal_value == kTracedPerfSignalValue) {
@@ -94,6 +110,7 @@
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "unrecognized profiling signal si_value: %d",
signal_value);
}
+ sigaction(SIGSYS, &old_act, nullptr);
}
// Open /proc/self/{maps,mem}, connect to traced_perf, send the fds over the
@@ -150,3 +167,33 @@
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to sendmsg: %s", strerror(errno));
}
}
+
+static void HandleSigsysSeccompOverride(int /*signal_number*/, siginfo_t* info,
+ void* void_context) {
+ ErrnoRestorer errno_restorer;
+ if (info->si_code != SYS_SECCOMP) {
+ return;
+ }
+
+ async_safe_format_log(
+ ANDROID_LOG_WARN, "libc",
+ "Profiling setup: trapped seccomp SIGSYS for syscall %d. Returning ENOSYS to caller.",
+ info->si_syscall);
+
+ // The handler is responsible for setting the return value as if the system
+ // call happened (which is arch-specific). Use a plausible unsuccessful value.
+ auto ret = -ENOSYS;
+ ucontext_t* ctx = reinterpret_cast<ucontext_t*>(void_context);
+
+#if defined(__arm__)
+ ctx->uc_mcontext.arm_r0 = ret;
+#elif defined(__aarch64__)
+ ctx->uc_mcontext.regs[0] = ret; // x0
+#elif defined(__i386__)
+ ctx->uc_mcontext.gregs[REG_EAX] = ret;
+#elif defined(__x86_64__)
+ ctx->uc_mcontext.gregs[REG_RAX] = ret;
+#else
+#error "unsupported architecture"
+#endif
+}