File indexing completed on 2024-04-21 11:27:33

0001 /*
0002     This file is part of the KDE Libraries
0003     SPDX-FileCopyrightText: 2000 Timo Hummel <timo.hummel@sap.com>
0004     SPDX-FileCopyrightText: 2000 Tom Braun <braunt@fh-konstanz.de>
0005     SPDX-FileCopyrightText: 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
0006     SPDX-FileCopyrightText: 2009 KDE e.V. <kde-ev-board@kde.org>
0007     SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0008     SPDX-FileContributor: 2009 Adriaan de Groot <groot@kde.org>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "kcrash.h"
0014 
0015 #include <config-kcrash.h>
0016 
0017 #include <signal.h>
0018 #include <stdio.h>
0019 #include <stdlib.h>
0020 #include <string.h>
0021 
0022 #include <qplatformdefs.h>
0023 #ifndef Q_OS_WIN
0024 #include <cerrno>
0025 #include <sys/resource.h>
0026 #include <sys/un.h>
0027 #else
0028 #include <qt_windows.h>
0029 #endif
0030 #ifdef Q_OS_LINUX
0031 #include <sys/poll.h>
0032 #include <sys/prctl.h>
0033 #endif
0034 
0035 #include <KAboutData>
0036 
0037 #include <algorithm>
0038 #include <array>
0039 #include <memory>
0040 
0041 #include <QDebug>
0042 #include <QDir>
0043 #include <QFile>
0044 #include <QGuiApplication>
0045 #include <QLibraryInfo>
0046 #include <QStandardPaths>
0047 #include <QThread>
0048 
0049 #include <QLoggingCategory>
0050 Q_DECLARE_LOGGING_CATEGORY(LOG_KCRASH)
0051 
0052 // logging category for this framework, default: log stuff >= info
0053 Q_LOGGING_CATEGORY(LOG_KCRASH, "kf.crash", QtInfoMsg)
0054 
0055 #if HAVE_X11
0056 #include <X11/Xlib.h>
0057 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0058 #include <QX11Info>
0059 #endif
0060 #endif
0061 
0062 #include "coreconfig_p.h"
0063 #include "metadata_p.h"
0064 
0065 // Copy from klauncher_cmds
0066 typedef struct {
0067     long cmd;
0068     long arg_length;
0069 } kcrash_launcher_header;
0070 
0071 #define LAUNCHER_OK 4
0072 #define LAUNCHER_EXEC_NEW 12
0073 
0074 namespace KCrash
0075 {
0076 KCRASH_EXPORT bool loadedByKdeinit = false;
0077 void setApplicationFilePath(const QString &filePath);
0078 // Create socket path to transfer ptrace scope and open connection
0079 }
0080 #ifdef Q_OS_LINUX
0081 static QByteArray s_socketpath;
0082 #endif
0083 
0084 struct Args {
0085     ~Args()
0086     {
0087         clear();
0088     }
0089 
0090     void clear()
0091     {
0092         if (!argc) {
0093             return;
0094         }
0095 
0096         for (int i = 0; i < argc; ++i) {
0097             delete[] argv[i];
0098         }
0099         delete[] argv;
0100 
0101         argv = nullptr;
0102         argc = 0;
0103     }
0104 
0105     void resize(int size)
0106     {
0107         clear();
0108         argc = size;
0109         argv = new char *[argc + 1];
0110         for (int i = 0; i < argc + 1; ++i) {
0111             argv[i] = nullptr;
0112         }
0113     }
0114 
0115     explicit operator bool() const
0116     {
0117         return argc > 0;
0118     }
0119 
0120     int argc = 0;
0121     // null-terminated array of null-terminated strings
0122     char **argv = nullptr;
0123 };
0124 
0125 static KCrash::HandlerType s_emergencySaveFunction = nullptr;
0126 static KCrash::HandlerType s_crashHandler = nullptr;
0127 static std::unique_ptr<char[]> s_appFilePath; // this is the actual QCoreApplication::applicationFilePath
0128 static std::unique_ptr<char[]> s_appName; // the binary name (may be altered by the application)
0129 static std::unique_ptr<char[]> s_appPath; // the binary dir path (may be altered by the application)
0130 static Args s_autoRestartCommandLine;
0131 static std::unique_ptr<char[]> s_drkonqiPath;
0132 static KCrash::CrashFlags s_flags = KCrash::CrashFlags();
0133 static int s_launchDrKonqi = -1; // -1=initial value 0=disabled 1=enabled
0134 static int s_originalSignal = -1;
0135 static QByteArray s_metadataPath;
0136 
0137 static std::unique_ptr<char[]> s_kcrashErrorMessage;
0138 Q_GLOBAL_STATIC(KCrash::CoreConfig, s_coreConfig)
0139 
0140 static void kcrashInitialize()
0141 {
0142     // Static because in some cases this is called multiple times
0143     // but if an application had any of the bad cases we always want
0144     // to skip the check
0145     static bool doAutoInitKCrash = true;
0146 
0147     if (!doAutoInitKCrash) {
0148         return;
0149     }
0150 
0151     QCoreApplication *app = QCoreApplication::instance();
0152     if (!app) {
0153         doAutoInitKCrash = false;
0154         return;
0155     }
0156 
0157     if (!QCoreApplication::startingUp()) {
0158         // If the app has already started, this means we're not being run as part of
0159         // qt_call_pre_routines, which most probably means that we're being run as part
0160         // of KCrash being loaded as part of some plugin of the app, so don't
0161         // do any magic
0162         doAutoInitKCrash = false;
0163         return;
0164     }
0165 
0166     if (!QCoreApplication::eventDispatcher()) {
0167         // We are called with event dispatcher being null when KCrash is being loaded
0168         // through plasma-integration instead of being linked to the app (i.e. QtCreator vs Okular)
0169         // For apps that don't link directly to KCrash do not do the magic
0170         doAutoInitKCrash = false;
0171         return;
0172     }
0173 
0174     KCrash::initialize();
0175 }
0176 Q_COREAPP_STARTUP_FUNCTION(kcrashInitialize)
0177 
0178 static QStringList libexecPaths()
0179 {
0180     // Static since we only need to evaluate once.
0181     static QStringList list = QFile::decodeName(qgetenv("LIBEXEC_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts) // env var is used first
0182         + QStringList{
0183             QCoreApplication::applicationDirPath(), // then look where our application binary is located
0184             QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath), // look where libexec path is (can be set in qt.conf)
0185             QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR) // look at our installation location
0186         };
0187     return list;
0188 }
0189 
0190 namespace KCrash
0191 {
0192 void setApplicationFilePath(const QString &filePath);
0193 void startProcess(int argc, const char *argv[], bool waitAndExit);
0194 
0195 #if defined(Q_OS_WIN)
0196 LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo);
0197 #endif
0198 }
0199 
0200 static bool shouldWriteMetadataToDisk()
0201 {
0202 #ifdef Q_OS_LINUX
0203     // NB: The daemon being currently running must not be a condition here. If something crashes during logout
0204     // the daemon may already be gone but we'll still want to deal with the crash on next login!
0205     // Similar reasoning applies to not checking the presence of the launcher socket.
0206     const bool drkonqiCoredumpHelper = !QStandardPaths::findExecutable(QStringLiteral("drkonqi-coredump-processor"), libexecPaths()).isEmpty();
0207     return s_coreConfig()->isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet("KCRASH_NO_METADATA");
0208 #else
0209     return false;
0210 #endif
0211 }
0212 
0213 void KCrash::initialize()
0214 {
0215     if (s_launchDrKonqi == 0) { // disabled by the program itself
0216         return;
0217     }
0218     const QStringList args = QCoreApplication::arguments();
0219     if (!qEnvironmentVariableIsSet("KDE_DEBUG") //
0220         && !qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") //
0221         && !qEnvironmentVariableIntValue("RUNNING_UNDER_RR") //
0222         && qEnvironmentVariableIntValue("KCRASH_DUMP_ONLY") == 0) {
0223         // enable drkonqi
0224         KCrash::setDrKonqiEnabled(true);
0225     } else {
0226         // This loads qtlogging.ini very early which prevents unittests from doing QStandardPaths::setTestModeEnabled(true) in initTestCase()
0227         // qCDebug(LOG_KCRASH) << "KCrash disabled through environment.";
0228     }
0229 
0230     if (QCoreApplication::instance()) {
0231         const QString path = QCoreApplication::applicationFilePath();
0232         s_appFilePath.reset(qstrdup(qPrintable(path))); // This intentionally cannot be changed by the application!
0233         KCrash::setApplicationFilePath(path);
0234     } else {
0235         qWarning() << "This process needs a QCoreApplication instance in order to use KCrash";
0236     }
0237 
0238 #ifdef Q_OS_LINUX
0239     // Create socket path to transfer ptrace scope and open connection
0240     s_socketpath = QFile::encodeName(QStringLiteral("%1/kcrash_%2").arg(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)).arg(getpid()));
0241 #endif
0242 
0243     if (shouldWriteMetadataToDisk()) {
0244         // We do not actively clean up metadata via KCrash but some other service. This potentially means we litter
0245         // a lot -> put the metadata in a subdir.
0246         const QString metadataDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata");
0247         if (QDir().mkpath(metadataDir)) {
0248             s_metadataPath = QFile::encodeName(metadataDir + QStringLiteral("/%1.ini").arg(QCoreApplication::applicationPid()));
0249         }
0250         if (!s_crashHandler) {
0251             // Always enable the default handler. We cannot create the metadata ahead of time since we do not know
0252             // when the application metadata is "complete".
0253             // TODO: kf6 maybe change the way init works and have the users run it when their done with kaboutdata etc.?
0254             //    the problem with delayed writing is that our crash handler (or any crash handler really) introduces a delay
0255             //    in dumping and this in turn increases the risk of another stepping into a puddle (SEGV only runs on the
0256             //    faulting thread; all other threads continue running!). therefore it'd be greatly preferred if we
0257             //    were able to write the metadata during initial app setup instead of when a crash occurs
0258             setCrashHandler(defaultCrashHandler);
0259         }
0260     } // empty s_metadataPath disables writing
0261 
0262     s_coreConfig(); // Initialize.
0263 }
0264 
0265 void KCrash::setEmergencySaveFunction(HandlerType saveFunction)
0266 {
0267     s_emergencySaveFunction = saveFunction;
0268 
0269     /*
0270      * We need at least the default crash handler for
0271      * emergencySaveFunction to be called
0272      */
0273     if (s_emergencySaveFunction && !s_crashHandler) {
0274         setCrashHandler(defaultCrashHandler);
0275     }
0276 }
0277 
0278 KCrash::HandlerType KCrash::emergencySaveFunction()
0279 {
0280     return s_emergencySaveFunction;
0281 }
0282 
0283 // Set the default crash handler in 10 seconds
0284 // This is used after an autorestart, the second instance of the application
0285 // is started with KCRASH_AUTO_RESTARTED=1, and we
0286 // set the defaultCrashHandler (to handle autorestart) after 10s.
0287 // The delay is to see if we stay up for more than 10s time, to avoid infinite
0288 // respawning if the app crashes on startup.
0289 class KCrashDelaySetHandler : public QObject
0290 {
0291 public:
0292     KCrashDelaySetHandler()
0293     {
0294         startTimer(10000); // 10 s
0295     }
0296 
0297 protected:
0298     void timerEvent(QTimerEvent *event) override
0299     {
0300         if (!s_crashHandler) { // not set meanwhile
0301             KCrash::setCrashHandler(KCrash::defaultCrashHandler);
0302         }
0303         killTimer(event->timerId());
0304         this->deleteLater();
0305     }
0306 };
0307 
0308 void KCrash::setFlags(KCrash::CrashFlags flags)
0309 {
0310     s_flags = flags;
0311     if (s_flags & AutoRestart) {
0312         // We need at least the default crash handler for autorestart to work.
0313         if (!s_crashHandler) {
0314             if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED")) {
0315                 new KCrashDelaySetHandler;
0316             } else {
0317                 setCrashHandler(defaultCrashHandler);
0318             }
0319         }
0320     }
0321 }
0322 
0323 void KCrash::setApplicationFilePath(const QString &filePath)
0324 {
0325     const int pos = filePath.lastIndexOf(QLatin1Char('/'));
0326     const QString appName = filePath.mid(pos + 1);
0327     const QString appPath = filePath.left(pos); // could be empty, in theory
0328 
0329     s_appName.reset(qstrdup(QFile::encodeName(appName).constData()));
0330     s_appPath.reset(qstrdup(QFile::encodeName(appPath).constData()));
0331 
0332     // Prepare the auto-restart command
0333     QStringList args = QCoreApplication::arguments();
0334     if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)...
0335         args.append(filePath);
0336     } else {
0337         args[0] = filePath; // replace argv[0] with full path above
0338     }
0339 
0340     s_autoRestartCommandLine.resize(args.count());
0341     for (int i = 0; i < args.count(); ++i) {
0342         s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(args.at(i)).constData());
0343     }
0344 }
0345 
0346 void KCrash::setDrKonqiEnabled(bool enabled)
0347 {
0348     const int launchDrKonqi = enabled ? 1 : 0;
0349     if (s_launchDrKonqi == launchDrKonqi) {
0350         return;
0351     }
0352     s_launchDrKonqi = launchDrKonqi;
0353     if (s_launchDrKonqi && !s_drkonqiPath) {
0354 #ifdef Q_OS_WINDOWS
0355         static const QString drkonqi = QStringLiteral("drkonqi.exe");
0356 #else
0357         static const QString drkonqi = QStringLiteral("drkonqi");
0358 #endif
0359         const QString exec = QStandardPaths::findExecutable(drkonqi, libexecPaths());
0360         if (exec.isEmpty()) {
0361             qCDebug(LOG_KCRASH) << "Could not find drkonqi in search paths:" << libexecPaths();
0362             s_launchDrKonqi = 0;
0363         } else {
0364             s_drkonqiPath.reset(qstrdup(qPrintable(exec)));
0365         }
0366     }
0367 
0368     // we need at least the default crash handler to launch drkonqi
0369     if (s_launchDrKonqi && !s_crashHandler) {
0370         setCrashHandler(defaultCrashHandler);
0371     }
0372 }
0373 
0374 bool KCrash::isDrKonqiEnabled()
0375 {
0376     return s_launchDrKonqi == 1;
0377 }
0378 
0379 void KCrash::setCrashHandler(HandlerType handler)
0380 {
0381 #if defined(Q_OS_WIN)
0382     static LPTOP_LEVEL_EXCEPTION_FILTER s_previousExceptionFilter = NULL;
0383 
0384     if (handler && !s_previousExceptionFilter) {
0385         s_previousExceptionFilter = SetUnhandledExceptionFilter(KCrash::win32UnhandledExceptionFilter);
0386     } else if (!handler && s_previousExceptionFilter) {
0387         SetUnhandledExceptionFilter(s_previousExceptionFilter);
0388         s_previousExceptionFilter = NULL;
0389     }
0390 #else
0391     if (!handler) {
0392         handler = SIG_DFL;
0393     }
0394 
0395     sigset_t mask;
0396     sigemptyset(&mask);
0397 
0398 #ifdef SIGSEGV
0399     signal(SIGSEGV, handler);
0400     sigaddset(&mask, SIGSEGV);
0401 #endif
0402 #ifdef SIGBUS
0403     signal(SIGBUS, handler);
0404     sigaddset(&mask, SIGBUS);
0405 #endif
0406 #ifdef SIGFPE
0407     signal(SIGFPE, handler);
0408     sigaddset(&mask, SIGFPE);
0409 #endif
0410 #ifdef SIGILL
0411     signal(SIGILL, handler);
0412     sigaddset(&mask, SIGILL);
0413 #endif
0414 #ifdef SIGABRT
0415     signal(SIGABRT, handler);
0416     sigaddset(&mask, SIGABRT);
0417 #endif
0418 
0419     sigprocmask(SIG_UNBLOCK, &mask, nullptr);
0420 #endif
0421 
0422     s_crashHandler = handler;
0423 }
0424 
0425 KCrash::HandlerType KCrash::crashHandler()
0426 {
0427     return s_crashHandler;
0428 }
0429 
0430 #if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
0431 static void closeAllFDs()
0432 {
0433     // Close all remaining file descriptors except for stdin/stdout/stderr
0434     struct rlimit rlp = {};
0435     getrlimit(RLIMIT_NOFILE, &rlp);
0436     for (rlim_t i = 3; i < rlp.rlim_cur; i++) {
0437         close(i);
0438     }
0439 }
0440 #endif
0441 
0442 void crashOnSigTerm(int sig)
0443 {
0444     Q_UNUSED(sig)
0445     raise(s_originalSignal);
0446 }
0447 
0448 void KCrash::defaultCrashHandler(int sig)
0449 {
0450     // WABA: Do NOT use qDebug() in this function because it is much too risky!
0451     // Handle possible recursions
0452     static int crashRecursionCounter = 0;
0453     crashRecursionCounter++; // Nothing before this, please !
0454     s_originalSignal = sig;
0455 
0456 #if !defined(Q_OS_WIN)
0457     signal(SIGALRM, SIG_DFL);
0458     alarm(3); // Kill me... (in case we deadlock in malloc)
0459 #endif
0460 
0461     if (crashRecursionCounter < 2) {
0462         if (s_emergencySaveFunction) {
0463             s_emergencySaveFunction(sig);
0464         }
0465         if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
0466             QThread::sleep(1);
0467             startProcess(s_autoRestartCommandLine.argc, const_cast<const char **>(s_autoRestartCommandLine.argv), false);
0468         }
0469         crashRecursionCounter++;
0470     }
0471 
0472     if (crashRecursionCounter < 3) {
0473         // If someone is telling me to stop while I'm already crashing, then I should resume crashing
0474         signal(SIGTERM, &crashOnSigTerm);
0475 
0476         // NB: all metadata writing ought to happen before closing FDs to reduce synchronization problems with dbus.
0477 
0478         // WARNING: do not forget to increase Metadata::argv's size when adding more potential arguments!
0479         Metadata data(s_drkonqiPath.get());
0480 #ifdef Q_OS_LINUX
0481         // The ini is required to be scoped here, as opposed to the conditional scope, so its lifetime is the same as
0482         // the regular data instance!
0483         MetadataINIWriter ini(s_metadataPath);
0484         if (ini.isWritable()) {
0485             // Add the canonical exe path so the coredump daemon has more data points to map metadata to journald entry.
0486             ini.add("--exe", s_appFilePath.get(), MetadataWriter::BoolValue::No);
0487             data.setAdditionalWriter(&ini);
0488         }
0489 #endif
0490 
0491         const QByteArray platformName = QGuiApplication::platformName().toUtf8();
0492         if (!platformName.isEmpty()) {
0493             data.add("--platform", platformName.constData());
0494         }
0495 
0496 #if HAVE_X11
0497         if (platformName == QByteArrayLiteral("xcb")) {
0498             // start up on the correct display
0499             char *display = nullptr;
0500 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0501             if (QX11Info::display()) {
0502                 display = XDisplayString(QX11Info::display());
0503 #else
0504             if (auto disp = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
0505                 display = XDisplayString(disp);
0506 #endif
0507             } else {
0508                 display = getenv("DISPLAY");
0509             }
0510             data.add("--display", display);
0511         }
0512 #endif
0513 
0514         data.add("--appname", s_appName ? s_appName.get() : "<unknown>");
0515 
0516         if (loadedByKdeinit) {
0517             data.addBool("--kdeinit");
0518         }
0519 
0520         // only add apppath if it's not NULL
0521         if (s_appPath && s_appPath[0]) {
0522             data.add("--apppath", s_appPath.get());
0523         }
0524 
0525         // signal number -- will never be NULL
0526         char sigtxt[10];
0527         sprintf(sigtxt, "%d", sig);
0528         data.add("--signal", sigtxt);
0529 
0530         char pidtxt[20];
0531         sprintf(pidtxt, "%lld", QCoreApplication::applicationPid());
0532         data.add("--pid", pidtxt);
0533 
0534         const KAboutData *about = KAboutData::applicationDataPointer();
0535         if (about) {
0536             if (about->internalVersion()) {
0537                 data.add("--appversion", about->internalVersion());
0538             }
0539 
0540             if (about->internalProgramName()) {
0541                 data.add("--programname", about->internalProgramName());
0542             }
0543 
0544             if (about->internalBugAddress()) {
0545                 data.add("--bugaddress", about->internalBugAddress());
0546             }
0547 
0548             if (about->internalProductName()) {
0549                 data.add("--productname", about->internalProductName());
0550             }
0551         }
0552 
0553         if (s_flags & SaferDialog) {
0554             data.addBool("--safer");
0555         }
0556 
0557         if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
0558             data.addBool("--restarted");
0559         }
0560 
0561 #if defined(Q_OS_WIN)
0562         char threadId[8] = {0};
0563         sprintf(threadId, "%d", GetCurrentThreadId());
0564         data.add("--thread", threadId);
0565 #endif
0566 
0567         data.close();
0568         const int argc = data.argc;
0569         const char **argv = data.argv.data();
0570 
0571 #ifndef NDEBUG
0572         fprintf(stderr, "KCrash: crashing... crashRecursionCounter = %d\n", crashRecursionCounter);
0573         fprintf(stderr,
0574                 "KCrash: Application Name = %s path = %s pid = %lld\n",
0575                 s_appName ? s_appName.get() : "<unknown>",
0576                 s_appPath ? s_appPath.get() : "<unknown>",
0577                 QCoreApplication::applicationPid());
0578         fprintf(stderr, "KCrash: Arguments: ");
0579         for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) {
0580             fprintf(stderr, "%s ", s_autoRestartCommandLine.argv[i]);
0581         }
0582         fprintf(stderr, "\n");
0583 #else
0584         fprintf(stderr, "KCrash: Application '%s' crashing...\n", s_appName ? s_appName.get() : "<unknown>");
0585 #endif
0586 
0587 #if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
0588         if (!(s_flags & KeepFDs)) {
0589             // This tries to prevent problems where applications fail to release resources that drkonqi might need.
0590             // Specifically this was introduced to ensure that an application that had grabbed the X11 cursor would
0591             // forcefully have it removed upon crash to ensure it is ungrabbed by the time drkonqi makes an appearance.
0592             // This is also the point in time when, for example, dbus services are lost. Closing the socket indicates
0593             // to dbus-daemon that the process has disappeared and it will forcefully reclaim the registered service names.
0594             //
0595             // Once we close our socket we lose potential dbus names and if we were running as a systemd service anchored to a name,
0596             // the daemon may decide to jump at us with a TERM signal. We'll want to have finished the metadata by now and
0597             // be near our tracing/raise().
0598             closeAllFDs();
0599         }
0600 #if HAVE_X11
0601 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0602         else if (QX11Info::display()) {
0603             close(ConnectionNumber(QX11Info::display()));
0604         }
0605 #else
0606         else if (auto display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
0607             close(ConnectionNumber(display));
0608         }
0609 #endif
0610 #endif
0611 #endif
0612 
0613         if (s_launchDrKonqi != 1) {
0614             setCrashHandler(nullptr);
0615 #if !defined(Q_OS_WIN)
0616             raise(sig); // dump core, or whatever is the default action for this signal.
0617 #endif
0618             return;
0619         }
0620 
0621         startProcess(argc, argv, true);
0622     }
0623 
0624     if (crashRecursionCounter < 4) {
0625         fprintf(stderr, "Unable to start Dr. Konqi\n");
0626     }
0627 
0628     if (s_coreConfig->isProcess()) {
0629         fprintf(stderr, "Re-raising signal for core dump handling.\n");
0630         KCrash::setCrashHandler(nullptr);
0631         raise(sig);
0632         // not getting here
0633     }
0634 
0635     _exit(255);
0636 }
0637 
0638 #if defined(Q_OS_WIN)
0639 
0640 void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
0641 {
0642     QString cmdLine;
0643     for (int i = 0; i < argc; ++i) {
0644         cmdLine.append(QLatin1Char('\"'));
0645         cmdLine.append(QFile::decodeName(argv[i]));
0646         cmdLine.append(QStringLiteral("\" "));
0647     }
0648 
0649     PROCESS_INFORMATION procInfo;
0650     STARTUPINFOW startupInfo =
0651         {sizeof(STARTUPINFO), 0, 0, 0, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
0652 
0653     bool success = CreateProcess(0, (wchar_t *)cmdLine.utf16(), NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startupInfo, &procInfo);
0654 
0655     if (success && waitAndExit) {
0656         // wait for child to exit
0657         WaitForSingleObject(procInfo.hProcess, INFINITE);
0658         _exit(253);
0659     }
0660 }
0661 
0662 // glue function for calling the unix signal handler from the windows unhandled exception filter
0663 LONG WINAPI KCrash::win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo)
0664 {
0665     // kdbgwin needs the context inside exceptionInfo because if getting the context after the
0666     // exception happened, it will walk down the stack and will stop at KiUserEventDispatch in
0667     // ntdll.dll, which is supposed to dispatch the exception from kernel mode back to user mode
0668     // so... let's create some shared memory
0669     HANDLE hMapFile = NULL;
0670     hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CONTEXT), TEXT("Local\\KCrashShared"));
0671 
0672     LPCTSTR pBuf = NULL;
0673     pBuf = (LPCTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT));
0674     CopyMemory((PVOID)pBuf, exceptionInfo->ContextRecord, sizeof(CONTEXT));
0675 
0676     if (s_crashHandler) {
0677         s_crashHandler(exceptionInfo->ExceptionRecord->ExceptionCode);
0678     }
0679 
0680     CloseHandle(hMapFile);
0681     return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate)
0682 }
0683 #else
0684 
0685 static pid_t startDirectly(const char *argv[]);
0686 
0687 #ifdef Q_OS_LINUX
0688 static int write_socket(int sock, char *buffer, int len);
0689 static int read_socket(int sock, char *buffer, int len);
0690 
0691 static int openDrKonqiSocket(const QByteArray &socketpath);
0692 static int pollDrKonqiSocket(pid_t pid, int sockfd);
0693 #endif
0694 
0695 void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
0696 {
0697     Q_UNUSED(argc);
0698     fprintf(stderr, "KCrash: Attempting to start %s\n", argv[0]);
0699 
0700     pid_t pid = startDirectly(argv);
0701 
0702     if (pid > 0 && waitAndExit) {
0703         // Seems we made it....
0704         alarm(0); // Stop the pending alarm that was set at the top of the defaultCrashHandler
0705 
0706         bool running = true;
0707         // Wait forever until the started process exits. This code path is executed
0708         // when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime
0709         // and only send SIGCONT when it is about to attach a debugger.
0710 #ifdef Q_OS_LINUX
0711         // Declare the process that will be debugging the crashed KDE app (#245529).
0712         // For now that will be DrKonqi, which may ask to transfer the ptrace scope to
0713         // a debugger it is not an ancestor of (because it was started via kdeinit or
0714         // KProcess::startDetached()) using a socket.
0715 #ifndef PR_SET_PTRACER
0716 #define PR_SET_PTRACER 0x59616d61
0717 #endif
0718         prctl(PR_SET_PTRACER, pid, 0, 0, 0);
0719 
0720         int sockfd = openDrKonqiSocket(s_socketpath);
0721 
0722         if (sockfd >= 0) {
0723             // Wait while DrKonqi is running and the socket connection exists
0724             // If the process was started directly, use waitpid(), as it's a child...
0725             while ((running = waitpid(pid, nullptr, WNOHANG) != pid) && pollDrKonqiSocket(pid, sockfd) >= 0) { }
0726             close(sockfd);
0727             unlink(s_socketpath.constData());
0728         }
0729 #endif
0730         if (running) {
0731             // If the process was started directly, use waitpid(), as it's a child...
0732             while (waitpid(pid, nullptr, 0) != pid) { }
0733         }
0734         if (!s_coreConfig->isProcess()) {
0735             // Only exit if we don't forward to core dumps
0736             _exit(253);
0737         }
0738     }
0739 }
0740 
0741 extern "C" char **environ;
0742 static pid_t startDirectly(const char *argv[])
0743 {
0744     char **environ_end;
0745     for (environ_end = environ; *environ_end; ++environ_end) { }
0746 
0747     std::array<const char *, 1024> environ_data; // hope it's big enough
0748     if ((unsigned)(environ_end - environ) + 2 >= environ_data.size()) {
0749         fprintf(stderr, "environ_data in KCrash not big enough!\n");
0750         return 0;
0751     }
0752     auto end = std::copy_if(environ, environ_end, environ_data.begin(), [](const char *s) {
0753         static const char envvar[] = "KCRASH_AUTO_RESTARTED=";
0754         return strncmp(envvar, s, sizeof(envvar) - 1) != 0;
0755     });
0756     *end++ = "KCRASH_AUTO_RESTARTED=1";
0757     *end++ = nullptr;
0758     pid_t pid = fork();
0759     switch (pid) {
0760     case -1:
0761         fprintf(stderr, "KCrash failed to fork(), errno = %d\n", errno);
0762         return 0;
0763     case 0:
0764         setgroups(0, nullptr); // Remove any extraneous groups
0765         if (setgid(getgid()) < 0 || setuid(getuid()) < 0) {
0766             _exit(253); // This cannot happen. Theoretically.
0767         }
0768 #ifndef Q_OS_OSX
0769         closeAllFDs(); // We are in the child now. Close FDs unconditionally.
0770 #endif
0771         execve(argv[0], const_cast<char **>(argv), const_cast<char **>(environ_data.data()));
0772         fprintf(stderr, "KCrash failed to exec(), errno = %d\n", errno);
0773         _exit(253);
0774     default:
0775         return pid;
0776     }
0777 }
0778 
0779 #ifdef Q_OS_LINUX
0780 
0781 /*
0782  * Write 'len' bytes from 'buffer' into 'sock'.
0783  * returns 0 on success, -1 on failure.
0784  */
0785 static int write_socket(int sock, char *buffer, int len)
0786 {
0787     ssize_t result;
0788     int bytes_left = len;
0789     while (bytes_left > 0) {
0790         result = write(sock, buffer, bytes_left);
0791         if (result > 0) {
0792             buffer += result;
0793             bytes_left -= result;
0794         } else if (result == 0) {
0795             return -1;
0796         } else if ((result == -1) && (errno != EINTR) && (errno != EAGAIN)) {
0797             return -1;
0798         }
0799     }
0800     return 0;
0801 }
0802 
0803 /*
0804  * Read 'len' bytes from 'sock' into 'buffer'.
0805  * returns 0 on success, -1 on failure.
0806  */
0807 static int read_socket(int sock, char *buffer, int len)
0808 {
0809     ssize_t result;
0810     int bytes_left = len;
0811     while (bytes_left > 0) {
0812         result = read(sock, buffer, bytes_left);
0813         if (result > 0) {
0814             buffer += result;
0815             bytes_left -= result;
0816         } else if (result == 0) {
0817             return -1;
0818         } else if ((result == -1) && (errno != EINTR) && (errno != EAGAIN)) {
0819             return -1;
0820         }
0821     }
0822     return 0;
0823 }
0824 
0825 static int openDrKonqiSocket(const QByteArray &socketpath)
0826 {
0827     int sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
0828     if (sockfd < 0) {
0829         perror("Warning: socket() for communication with DrKonqi failed");
0830         return -1;
0831     }
0832 
0833     struct sockaddr_un drkonqi_server;
0834     drkonqi_server.sun_family = AF_UNIX;
0835 
0836     if (socketpath.size() >= static_cast<int>(sizeof(drkonqi_server.sun_path))) {
0837         fprintf(stderr, "Warning: socket path is too long\n");
0838         close(sockfd);
0839         return -1;
0840     }
0841     strcpy(drkonqi_server.sun_path, socketpath.constData());
0842 
0843     unlink(drkonqi_server.sun_path); // remove potential stale socket
0844     if (bind(sockfd, (struct sockaddr *)&drkonqi_server, sizeof(drkonqi_server)) < 0) {
0845         perror("Warning: bind() for communication with DrKonqi failed");
0846         close(sockfd);
0847         unlink(drkonqi_server.sun_path);
0848         return -1;
0849     }
0850 
0851     listen(sockfd, 1);
0852 
0853     return sockfd;
0854 }
0855 
0856 static int pollDrKonqiSocket(pid_t pid, int sockfd)
0857 {
0858     struct pollfd fd;
0859     fd.fd = sockfd;
0860     fd.events = POLLIN;
0861     int r;
0862     do {
0863         r = poll(&fd, 1, 1000); // wait for 1 second for a request by DrKonqi
0864     } while (r == -1 && errno == EINTR);
0865     // only continue if POLLIN event returned
0866     if (r == 0) { // timeout
0867         return 0;
0868     } else if (r == -1 || !(fd.revents & POLLIN)) { // some error
0869         return -1;
0870     }
0871 
0872     static struct sockaddr_un drkonqi_client;
0873     static socklen_t cllength = sizeof(drkonqi_client);
0874     int clsockfd;
0875     do {
0876         clsockfd = accept(sockfd, (struct sockaddr *)&drkonqi_client, &cllength);
0877     } while (clsockfd == -1 && errno == EINTR);
0878     if (clsockfd < 0) {
0879         return -1;
0880     }
0881 
0882     // check whether the message is coming from DrKonqi
0883     static struct ucred ucred;
0884     static socklen_t credlen = sizeof(struct ucred);
0885     if (getsockopt(clsockfd, SOL_SOCKET, SO_PEERCRED, &ucred, &credlen) < 0) {
0886         return -1;
0887     }
0888 
0889     if (ucred.pid != pid) {
0890         fprintf(stderr, "Warning: peer pid does not match DrKonqi pid\n");
0891         return -1;
0892     }
0893 
0894     // read PID to change ptrace scope
0895     static const int msize = 21; // most digits in a 64bit int (+sign +'\0')
0896     char msg[msize];
0897     if (read_socket(clsockfd, msg, msize) == 0) {
0898         int dpid = atoi(msg);
0899         prctl(PR_SET_PTRACER, dpid, 0, 0, 0);
0900         // confirm change to DrKonqi
0901         if (write_socket(clsockfd, msg, msize) == 0) {
0902             fprintf(stderr, "KCrash: ptrace access transferred to %s\n", msg);
0903         }
0904     }
0905     close(clsockfd);
0906 
0907     return 1;
0908 }
0909 
0910 #endif
0911 
0912 #endif // Q_OS_UNIX
0913 
0914 void KCrash::setErrorMessage(const QString &message)
0915 {
0916     s_kcrashErrorMessage.reset(qstrdup(message.toUtf8().constData()));
0917 }