setjmp/longjmp: avoid invalid values in the stack pointer.
arm64 was already being careful, but x86/x86-64 and 32-bit ARM could be
caught by a signal in a state where the stack pointer was mangled.
For 32-bit ARM I've taken care with the link register too, to avoid
potential issues with unwinding.
Bug: http://b/152210274
Test: treehugger
Change-Id: I1ce285b017a633c732dbe04743368f4cae27af85
diff --git a/tests/setjmp_test.cpp b/tests/setjmp_test.cpp
index e6b6819..4b1482a 100644
--- a/tests/setjmp_test.cpp
+++ b/tests/setjmp_test.cpp
@@ -18,6 +18,8 @@
#include <setjmp.h>
#include <stdlib.h>
+#include <sys/syscall.h>
+#include <unistd.h>
#include "BionicDeathTest.h"
#include "SignalUtils.h"
@@ -268,3 +270,57 @@
if (value == 0) call_longjmp(buf);
EXPECT_EQ(123, value);
}
+
+TEST(setjmp, bug_152210274) {
+ // Ensure that we never have a mangled value in the stack pointer.
+#if defined(__BIONIC__)
+ struct sigaction sa = {.sa_flags = SA_SIGINFO, .sa_sigaction = [](int, siginfo_t*, void*) {}};
+ ASSERT_EQ(0, sigaction(SIGPROF, &sa, 0));
+
+ constexpr size_t kNumThreads = 20;
+
+ // Start a bunch of threads calling setjmp/longjmp.
+ auto jumper = [](void* arg) -> void* {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGPROF);
+ pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
+
+ jmp_buf buf;
+ for (size_t count = 0; count < 100000; ++count) {
+ if (setjmp(buf) != 0) {
+ perror("setjmp");
+ abort();
+ }
+ if (*static_cast<pid_t*>(arg) == 100) longjmp(buf, 1);
+ }
+ return nullptr;
+ };
+ pid_t tids[kNumThreads] = {};
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ pthread_t t;
+ ASSERT_EQ(0, pthread_create(&t, nullptr, jumper, &tids[i]));
+ tids[i] = pthread_gettid_np(t);
+ }
+
+ // Start the interrupter thread.
+ auto interrupter = [](void* arg) -> void* {
+ pid_t* tids = static_cast<pid_t*>(arg);
+ for (size_t count = 0; count < 1000; ++count) {
+ for (size_t i = 0; i < kNumThreads; i++) {
+ if (tgkill(getpid(), tids[i], SIGPROF) == -1 && errno != ESRCH) {
+ perror("tgkill failed");
+ abort();
+ }
+ }
+ usleep(100);
+ }
+ return nullptr;
+ };
+ pthread_t t;
+ ASSERT_EQ(0, pthread_create(&t, nullptr, interrupter, tids));
+ pthread_join(t, nullptr);
+#else
+ GTEST_LOG_(INFO) << "tests uses functions not in glibc";
+#endif
+}