diff options
-rw-r--r-- | src/client/linux/handler/exception_handler.cc | 29 | ||||
-rw-r--r-- | src/client/linux/handler/exception_handler_unittest.cc | 31 |
2 files changed, 60 insertions, 0 deletions
diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc index aad2a0e0..9a56c14b 100644 --- a/src/client/linux/handler/exception_handler.cc +++ b/src/client/linux/handler/exception_handler.cc @@ -292,6 +292,35 @@ void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { // All the exception signals are blocked at this point. pthread_mutex_lock(&handler_stack_mutex_); + // Sometimes, Breakpad runs inside a process where some other buggy code + // saves and restores signal handlers temporarily with 'signal' + // instead of 'sigaction'. This loses the SA_SIGINFO flag associated + // with this function. As a consequence, the values of 'info' and 'uc' + // become totally bogus, generally inducing a crash. + // + // The following code tries to detect this case. When it does, it + // resets the signal handlers with sigaction + SA_SIGINFO and returns. + // This forces the signal to be thrown again, but this time the kernel + // will call the function with the right arguments. + struct sigaction cur_handler; + if (sigaction(sig, NULL, &cur_handler) == 0 && + (cur_handler.sa_flags & SA_SIGINFO) == 0) { + // Reset signal handler with the right flags. + sigemptyset(&cur_handler.sa_mask); + sigaddset(&cur_handler.sa_mask, sig); + + cur_handler.sa_sigaction = SignalHandler; + cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO; + + if (sigaction(sig, &cur_handler, NULL) == -1) { + // When resetting the handler fails, try to reset the + // default one to avoid an infinite loop here. + signal(sig, SIG_DFL); + } + pthread_mutex_unlock(&handler_stack_mutex_); + return; + } + bool handled = false; for (int i = handler_stack_->size() - 1; !handled && i >= 0; --i) { handled = (*handler_stack_)[i]->HandleSignal(sig, info, uc); diff --git a/src/client/linux/handler/exception_handler_unittest.cc b/src/client/linux/handler/exception_handler_unittest.cc index 890061e6..bb463510 100644 --- a/src/client/linux/handler/exception_handler_unittest.cc +++ b/src/client/linux/handler/exception_handler_unittest.cc @@ -317,6 +317,37 @@ TEST(ExceptionHandlerTest, RedeliveryToDefaultHandler) { ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); } +// Check that saving and restoring the signal handler with 'signal' +// instead of 'sigaction' doesn't make the Breakpad signal handler +// crash. See comments in ExceptionHandler::SignalHandler for full +// details. +TEST(ExceptionHandlerTest, RedeliveryOnBadSignalHandlerFlag) { + AutoTempDir temp_dir; + const pid_t child = fork(); + if (child == 0) { + // Install the RaiseSIGKILL handler for SIGSEGV. + ASSERT_TRUE(InstallRaiseSIGKILL()); + + // Create a new exception handler, this installs a new SIGSEGV + // handler, after saving the old one. + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallbackReturnFalse, NULL, true, -1); + + // Install the default SIGSEGV handler, saving the current one. + // Then re-install the current one with 'signal', this loses the + // SA_SIGINFO flag associated with the Breakpad handler. + sighandler_t old_handler = signal(SIGSEGV, SIG_DFL); + ASSERT_NE(old_handler, SIG_ERR); + ASSERT_NE(signal(SIGSEGV, old_handler), SIG_ERR); + + // Crash with the exception handler in scope. + *reinterpret_cast<volatile int*>(NULL) = 0; + } + // SIGKILL means Breakpad's signal handler didn't crash. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + TEST(ExceptionHandlerTest, StackedHandlersDeliveredToTop) { AutoTempDir temp_dir; |