[education/kstars] kstars/auxiliary: Fix crash when a stale EkosLive dialog response arrives after the dialog was dismissed

Jasem Mutlaq <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 2fb72b3135d957dd0b743b26dbb573b29dbc46d4 by Jasem Mutlaq, on behalf of Ilia Belov.
Committed on 28/07/2026 at 07:03.
Pushed by mutlaqja into branch 'master'.

Fix crash when a stale EkosLive dialog response arrives after the dialog was dismissed

`KSMessageBox` is a reused singleton; buttons of a dismissed dialog stay as its children until the next dialog calls `reset()`. `selectResponse()` matches a button by text and clicks it unconditionally, and `Message::processDialogResponse()` forwards remote `dialog_get_response` frames without validation. So a response sent after the dialog was already dismissed (closed on the desktop, timed out, or a duplicate tap) clicks a stale button, re-fires `accepted()`/`rejected()` and crashes KStars with SIGSEGV during widget teardown.

Reproduce:

1. Open the StellarMate app.
2. Trigger any Ekos dialog ("Cover the telescope…").
3. Dismiss it on the KStars desktop.
4. Tap a button for that dialog in the app → crash.

Fix: in `selectResponse()`, ignore responses when the dialog is not visible, and ignore duplicates while a response is in flight (`m_ResponseInProgress`, cleared on accepted/rejected/reset). Normal single-response behavior unchanged.

M  +17   -0    kstars/auxiliary/ksmessagebox.cpp
M  +5    -0    kstars/auxiliary/ksmessagebox.h

https://invent.kde.org/education/kstars/-/commit/2fb72b3135d957dd0b743b26dbb573b29dbc46d4

diff --git a/kstars/auxiliary/ksmessagebox.cpp b/kstars/auxiliary/ksmessagebox.cpp
index 65ba058bbe..cb4f0c51a4 100644
--- a/kstars/auxiliary/ksmessagebox.cpp
+++ b/kstars/auxiliary/ksmessagebox.cpp
@@ -46,12 +46,14 @@ KSMessageBox::KSMessageBox() : QMessageBox()
 
     connect(this, &KSMessageBox::rejected, [this]()
     {
+        m_ResponseInProgress = false;
         m_ProgressTimer.stop();
         Q_EMIT newMessage(QJsonObject());
     });
 
     connect(this, &KSMessageBox::accepted, [this]()
     {
+        m_ResponseInProgress = false;
         m_ProgressTimer.stop();
         Q_EMIT newMessage(QJsonObject());
     });
@@ -157,6 +159,7 @@ void KSMessageBox::setupTimeout(quint32 timeout)
 
 void KSMessageBox::reset()
 {
+    m_ResponseInProgress = false;
     m_ProgressTimer.stop();
     resetTimeout();
 
@@ -296,12 +299,26 @@ QJsonObject KSMessageBox::createMessageObject()
 
 bool KSMessageBox::selectResponse(const QString &button)
 {
+    // Ignore responses when no dialog is currently shown. The message box is a
+    // reused singleton, so the buttons of a previously dismissed dialog linger as
+    // children; clicking one of those would re-emit accepted()/rejected() and could
+    // tear down widgets while their signals are still being processed.
+    if (!isVisible())
+        return false;
+
+    // Ignore duplicate or re-entrant responses. animateClick() dismisses the dialog
+    // asynchronously, so a second response (e.g. a remote EkosLive client sending
+    // dialog_get_response repeatedly) could arrive before the dialog hides.
+    if (m_ResponseInProgress)
+        return false;
+
     for (const auto oneButton : findChildren<QPushButton * >())
     {
         const QString buttonText = oneButton->text().remove("&");
 
         if (button == buttonText)
         {
+            m_ResponseInProgress = true;
             oneButton->animateClick();
             return true;
         }
diff --git a/kstars/auxiliary/ksmessagebox.h b/kstars/auxiliary/ksmessagebox.h
index 0b8f8a71b9..079f90c242 100644
--- a/kstars/auxiliary/ksmessagebox.h
+++ b/kstars/auxiliary/ksmessagebox.h
@@ -63,6 +63,11 @@ class KSMessageBox: public QMessageBox
         // Dialog timeout in seconds
         quint32 m_Timeout {60};
 
+        // True while a button response is being processed. Guards against duplicate
+        // or stale programmatic responses (e.g. from a remote EkosLive client) that
+        // would otherwise click a button on an already-dismissed dialog.
+        bool m_ResponseInProgress {false};
+
         static KSMessageBox *m_Instance;
 
         void reset();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.