summaryrefslogtreecommitdiffstats
path: root/src/testlib/qtestcase.cpp
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2022-05-02 23:13:42 -0700
committerThiago Macieira <thiago.macieira@intel.com>2022-05-30 15:56:26 -0700
commitd5f4f91c3c2e6b99b671fd4426a397247b7be4fe (patch)
treecb720d893d42192f643631a0ad1b0c7fe8f6b147 /src/testlib/qtestcase.cpp
parente390ff050a8fef871adc13c5e6e8acb16c71617d (diff)
FatalSignalHandler: use mmap() to create the alternate stack
So we can mark the bottom page as inaccessible, thus be able to catch a stack overflow in the handler itself. Our code shouldn't cause overflows, but it's possible that a chained handler does more work than expected. Pick-to: 6.3 Change-Id: I5ff8e16fcdcb4ffd9ab6fffd16eb83a294ab7958 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src/testlib/qtestcase.cpp')
-rw-r--r--src/testlib/qtestcase.cpp88
1 files changed, 67 insertions, 21 deletions
diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp
index 80f656fcd3..b42ab6f5f9 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -82,12 +82,16 @@
#include <errno.h>
#include <signal.h>
#include <time.h>
+#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
# if !defined(Q_OS_INTEGRITY)
# include <sys/resource.h>
# endif
+# ifndef SIGSTKSZ
+# define SIGSTKSZ 0 /* we have code to set the minimum */
+# endif
# ifndef SA_RESETHAND
# define SA_RESETHAND 0
# endif
@@ -1865,7 +1869,7 @@ public:
oldActions().fill(act);
// Remove the handler after it is invoked.
- act.sa_flags = SA_RESETHAND;
+ act.sa_flags = SA_RESETHAND | setupAlternateStack();
# ifdef SA_SIGINFO
act.sa_flags |= SA_SIGINFO;
@@ -1874,26 +1878,6 @@ public:
act.sa_handler = FatalSignalHandler::regularHandler;
# endif
- // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
- // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
-# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
- // Let the signal handlers use an alternate stack
- // This is necessary if SIGSEGV is to catch a stack overflow
-# if defined(Q_CC_GNU) && defined(Q_OF_ELF)
- // Put the alternate stack in the .lbss (large BSS) section so that it doesn't
- // interfere with normal .bss symbols
- __attribute__((section(".lbss.altstack"), aligned(4096)))
-# endif
- static QVarLengthArray<char, 32 * 1024> alternateStack;
- alternateStack.resize(qMax(SIGSTKSZ, alternateStack.size()));
- stack_t stack;
- stack.ss_flags = 0;
- stack.ss_size = alternateStack.size();
- stack.ss_sp = alternateStack.data();
- sigaltstack(&stack, nullptr);
- act.sa_flags |= SA_ONSTACK;
-# endif
-
// Block all fatal signals in our signal handler so we don't try to close
// the testlog twice.
sigemptyset(&act.sa_mask);
@@ -1926,6 +1910,8 @@ public:
if (isOurs(action))
sigaction(fatalSignals[i], &act, nullptr);
}
+
+ freeAlternateStack();
}
private:
@@ -1937,6 +1923,64 @@ private:
return oldActions;
}
+ auto alternateStackSize()
+ {
+ struct R { size_t size, pageSize; };
+ static constexpr size_t MinStackSize = 32 * 1024;
+ size_t pageSize = sysconf(_SC_PAGESIZE);
+ size_t size = SIGSTKSZ;
+ if (size < MinStackSize) {
+ size = MinStackSize;
+ } else {
+ // round up to a page
+ size = (size + pageSize - 1) & -pageSize;
+ }
+
+ return R{ size + pageSize, pageSize };
+ }
+
+ int setupAlternateStack()
+ {
+ // tvOS/watchOS both define SA_ONSTACK (in sys/signal.h) but mark sigaltstack() as
+ // unavailable (__WATCHOS_PROHIBITED __TVOS_PROHIBITED in signal.h)
+# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
+ // Let the signal handlers use an alternate stack
+ // This is necessary if SIGSEGV is to catch a stack overflow
+ auto r = alternateStackSize();
+ int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+# ifdef MAP_STACK
+ flags |= MAP_STACK;
+# endif
+ alternateStackBase = mmap(nullptr, r.size, PROT_READ | PROT_WRITE, flags, -1, 0);
+ if (alternateStackBase == MAP_FAILED)
+ return 0;
+
+ // mark the bottom page inaccessible, to catch a handler stack overflow
+ (void) mprotect(alternateStackBase, r.pageSize, PROT_NONE);
+
+ stack_t stack;
+ stack.ss_flags = 0;
+ stack.ss_size = r.size - r.pageSize;
+ stack.ss_sp = static_cast<char *>(alternateStackBase) + r.pageSize;
+ sigaltstack(&stack, nullptr);
+ return SA_ONSTACK;
+# else
+ return 0;
+# endif
+ }
+
+ void freeAlternateStack()
+ {
+# if defined(SA_ONSTACK) && !defined(Q_OS_TVOS) && !defined(Q_OS_WATCHOS)
+ if (alternateStackBase != MAP_FAILED) {
+ stack_t stack = {};
+ stack.ss_flags = SS_DISABLE;
+ sigaltstack(&stack, nullptr);
+ munmap(alternateStackBase, alternateStackSize().size);
+ }
+# endif
+ }
+
static void actionHandler(int signum, siginfo_t * /* info */, void * /* ucontext */)
{
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
@@ -1982,6 +2026,8 @@ private:
{
actionHandler(signum, nullptr, nullptr);
}
+
+ void *alternateStackBase = MAP_FAILED;
static bool pauseOnCrash;
};
bool FatalSignalHandler::pauseOnCrash = false;