Skip to content

Crash signal handler calls non-async-signal-safe functions #714

@grisutheguru

Description

@grisutheguru

Summary

The POSIX signal handlers in src/platform/posixsignalnotifier.cpp call functions that are not async-signal-safe, which can cause deadlocks, corruption, or secondary crashes during crash handling.

Details

Crash handler (lines 85-96)

action.sa_handler = [](int sig) {
    constexpr struct sigaction default_action = {};
    ::sigaction(sig, &default_action, nullptr);    // OK: async-signal-safe
    qCritical("Crash signal %d received", sig);    // NOT async-signal-safe
    Stacktrace::process([](const Stacktrace::Frame& frame) {
        qCritical() << frame;                      // NOT async-signal-safe
    });
};

qCritical() uses Qt's logging infrastructure (locks, allocations, I/O). Stacktrace::process() calls dladdr(), backtrace(), and abi::__cxa_demangle() -- all of which allocate memory. If the crash occurred while malloc's lock was held, these calls deadlock.

Unwatch handler (lines 153-162)

action.sa_handler = [](int sig) {
    qWarning("Signal %d received twice; terminating ungracefully", sig);  // NOT safe
    ::exit(EXIT_FAILURE);  // NOT safe! Runs atexit handlers, flushes buffers
};

::exit() runs atexit handlers and flushes stdio, which can deadlock in signal context. Must use _exit().

Suggested Fix

// Crash handler - use only async-signal-safe functions
action.sa_handler = [](int sig) {
    constexpr struct sigaction default_action = {};
    ::sigaction(sig, &default_action, nullptr);
    // write() is async-signal-safe
    const char msg[] = "Fatal signal received\n";
    (void)::write(STDERR_FILENO, msg, sizeof(msg) - 1);
    // Re-raise to get core dump with default handler
    ::raise(sig);
};

// Unwatch handler
action.sa_handler = [](int sig) {
    const char msg[] = "Signal received twice; terminating\n";
    (void)::write(STDERR_FILENO, msg, sizeof(msg) - 1);
    _exit(EXIT_FAILURE);  // _exit, NOT exit
};

For stack traces, consider sigaltstack + fork() to collect in a child process, or accept that crash-time stack traces are best-effort.

Impact

Signal handlers can deadlock or crash during crash handling, preventing useful diagnostics and potentially leaving the application in an unresponsive state instead of terminating cleanly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions